mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148)
Updates HackRF native byte buffers with automatic DC removal. Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer. Resolves issue in RTL native buffer processing. Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2. Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks. Removes last of Reusable buffer code. Updates DFTProcessor to use a single thread scheduled executor. Updates Polyphase channelizer to use threads instead of an executor. Removes a number of unused classes. Commenting out window calibration. Scalar is faster in both implementations. Adds vector and scalar implementations for windows. Repackages window and window type classes. WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators. WIP adds vector implementations for Airspy interleaved buffer iterator. Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing. Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection. Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers. Updates polyphase channelizer to be more efficient in output channel processing. Updates ViterbiDecoder to change Map usage to array for memory efficiency. Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width. Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains. Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object. Still to resolve is the ComplexMixer implementations. Needs validation and a calibration implementation with an update to the complex mixer factory. Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls. Updates the AM demodulator for separated I&Q float sample processing. Code review and testing changes. updates build script for runtime configuration. Integrates calibration manager with main sdrtrunk application. Updates NBFMDecoder to use new optimized FIR and Decimation filter options. Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation. Adds calibration manager plugins. Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components. Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats. Adds vector implementation of FM demodulator. This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing. This new complex sample interface will be used across the codebase eventually. Adds ComplexOscillator and a SIMD version VectorComplexOscillator. Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster. The vectorized version is about 43% faster when SIMD lane width is 8x. Adds new vector complex filters and new real oscillators. Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation. Will replace this with a complex oscillator that only uses the real part. Updates ComplexFIRFilter to be 50% more efficient. Adds 23 tap filter implementations. Will not add 63-tap implementations because they are slower than the default vector implementation. Adds 15-tap customized vector half band filters. Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters.. Adds vector complex half band filter implementations. Note: performance of the generic 64-bit 2-lane filter is horrible and is not included. Optimizes complex half-band filter to be 27% more efficient. Implements vectorized real half-band filter implementations. Implements a new FIR filter class that processes sample buffers instead of single sample at a time. Refactored all of the calibratons to be time-duration based. Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x. Changed gradle jvm args so that the windows plaf export is always applied. Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images. Testing tweaks and changes. Adds logging limiter to dispatcher for buffer overflows Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations. Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system. Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -2,6 +2,7 @@
|
||||
<dictionary name="denny">
|
||||
<words>
|
||||
<w>bahroo</w>
|
||||
<w>basebanded</w>
|
||||
<w>broadcastify</w>
|
||||
<w>correlator</w>
|
||||
<w>deinterleave</w>
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="liberica-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="liberica-17.0.2-fx" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
138
build.gradle
138
build.gradle
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 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
|
||||
@@ -20,7 +20,6 @@ plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.10'
|
||||
id 'org.beryx.runtime' version '1.12.7'
|
||||
}
|
||||
|
||||
@@ -32,12 +31,6 @@ repositories {
|
||||
version = '0.5.0-alpha7'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
javafx {
|
||||
version = "17.0.1"
|
||||
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
|
||||
configuration = "implementation"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs 'src/main'
|
||||
test.java.srcDirs 'src/test'
|
||||
@@ -47,12 +40,6 @@ test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
configurations {
|
||||
javafx_libs_linux
|
||||
javafx_libs_osx
|
||||
javafx_libs_win
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// JUnit Tests
|
||||
@@ -100,53 +87,21 @@ dependencies {
|
||||
implementation 'org.usb4java:usb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
|
||||
//JavaFX libraries for Linux
|
||||
javafx_libs_linux "org.openjfx:javafx-base:$javafx.version:linux"
|
||||
javafx_libs_linux "org.openjfx:javafx-controls:$javafx.version:linux"
|
||||
javafx_libs_linux "org.openjfx:javafx-graphics:$javafx.version:linux"
|
||||
javafx_libs_linux "org.openjfx:javafx-swing:$javafx.version:linux"
|
||||
|
||||
//JavaFX libraries for OSX
|
||||
javafx_libs_osx "org.openjfx:javafx-base:$javafx.version:mac"
|
||||
javafx_libs_osx "org.openjfx:javafx-controls:$javafx.version:mac"
|
||||
javafx_libs_osx "org.openjfx:javafx-graphics:$javafx.version:mac"
|
||||
javafx_libs_osx "org.openjfx:javafx-swing:$javafx.version:mac"
|
||||
|
||||
//JavaFX libraries for Windows
|
||||
javafx_libs_win "org.openjfx:javafx-base:$javafx.version:win"
|
||||
javafx_libs_win "org.openjfx:javafx-controls:$javafx.version:win"
|
||||
javafx_libs_win "org.openjfx:javafx-graphics:$javafx.version:win"
|
||||
javafx_libs_win "org.openjfx:javafx-swing:$javafx.version:win"
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("io.github.dsheirer.gui.SDRTrunk")
|
||||
|
||||
//Note: jide-oss.jar requires access to hidden windows look & feel
|
||||
//Note: controlsfx.jar requires access to hidden javaFX classes
|
||||
applicationDefaultJvmArgs = ['--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED',
|
||||
'--add-exports=javafx.base/com.sun.javafx.event=org.controlsfx.controls']
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime JVM arguments. JDK 17 has hidden a number of classes that are no longer exported from the
|
||||
* modules. Some legacy support libraries still require access to these classes, so we explicitly
|
||||
* add them to be exported from those modules here.
|
||||
* This is needed for the JDK17 vector API ... until it moves out of incubation
|
||||
*/
|
||||
import org.gradle.internal.os.OperatingSystem;
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add("--add-modules=jdk.incubator.vector")
|
||||
}
|
||||
|
||||
run {
|
||||
if(OperatingSystem.current().isWindows()) {
|
||||
//Note: jide-oss.jar requires access to hidden windows look & feel
|
||||
//Note: controlsfx.jar requires access to hidden javaFX classes
|
||||
jvmArgs = ['--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED',
|
||||
'--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED']
|
||||
}
|
||||
else {
|
||||
//Note: controlsfx.jar requires access to hidden javaFX classes
|
||||
jvmArgs = ['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED']
|
||||
}
|
||||
application {
|
||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||
applicationDefaultJvmArgs =
|
||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
|
||||
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
|
||||
}
|
||||
|
||||
jar {
|
||||
@@ -177,13 +132,13 @@ idea {
|
||||
*/
|
||||
def jdk_base = '/home/denny/java_jdks/'
|
||||
|
||||
def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-17.0.1-full'
|
||||
def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-17.0.1-full'
|
||||
def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-17.0.1-full.jdk'
|
||||
def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-17.0.1-full'
|
||||
def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-17.0.2-full'
|
||||
def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-17.0.2-full'
|
||||
def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-17.0.2-full.jdk'
|
||||
def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-17.0.2-full'
|
||||
|
||||
def hasTargetJdk = file(jdk_linux_x86_64).exists() ||
|
||||
file(jdk_linux_arm64) ||
|
||||
file(jdk_linux_arm64).exists() ||
|
||||
file(jdk_osx_x86_64).exists() ||
|
||||
file(jdk_windows_x86_64).exists()
|
||||
|
||||
@@ -230,66 +185,5 @@ runtime {
|
||||
}
|
||||
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http',
|
||||
'java.sql', 'jdk.crypto.ec']
|
||||
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
}
|
||||
|
||||
def image_lib_linux_arm64 = file("$buildDir/image/sdr-trunk-linux-arm64-v" + version + "/lib")
|
||||
def image_lib_linux_x86_64 = file("$buildDir/image/sdr-trunk-linux-x86_64-v" + version + "/lib")
|
||||
def image_lib_osx_x86_64 = file("$buildDir/image/sdr-trunk-osx-x86_64-v" + version + "/lib")
|
||||
def image_lib_win_x86_64 = file("$buildDir/image/sdr-trunk-windows-x86_64-v" + version + "/lib")
|
||||
|
||||
/**
|
||||
* Copy JavaFX OS-specific libraries for each JDK to each platform target directory
|
||||
*/
|
||||
tasks.runtime.doLast
|
||||
{
|
||||
if(file(jdk_linux_arm64).exists())
|
||||
{
|
||||
//Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build
|
||||
delete(fileTree(image_lib_linux_arm64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ })
|
||||
|
||||
//Copy OS-specific JavaFX libraries
|
||||
copy {
|
||||
from configurations.javafx_libs_linux
|
||||
into image_lib_linux_arm64
|
||||
}
|
||||
}
|
||||
|
||||
if(file(jdk_linux_x86_64).exists())
|
||||
{
|
||||
//Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build
|
||||
delete(fileTree(image_lib_linux_x86_64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ })
|
||||
|
||||
//Copy OS-specific JavaFX libraries
|
||||
copy {
|
||||
from configurations.javafx_libs_linux
|
||||
into image_lib_linux_x86_64
|
||||
}
|
||||
}
|
||||
|
||||
if(file(jdk_osx_x86_64).exists())
|
||||
{
|
||||
//Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build
|
||||
delete(fileTree(image_lib_osx_x86_64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ })
|
||||
|
||||
//Copy OS-specific JavaFX libraries
|
||||
copy {
|
||||
from configurations.javafx_libs_osx
|
||||
into image_lib_osx_x86_64
|
||||
}
|
||||
}
|
||||
|
||||
if(file(jdk_windows_x86_64).exists())
|
||||
{
|
||||
//Delete any host system OS-specific JavaFX JDK modules that were inserted as part of default build
|
||||
delete(fileTree(image_lib_win_x86_64).include { it.name ==~ /javafx.*-(win|mac|linux)\.jar/ })
|
||||
|
||||
//Copy OS-specific JavaFX libraries
|
||||
copy {
|
||||
from configurations.javafx_libs_win
|
||||
into image_lib_win_x86_64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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,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.alias.action;
|
||||
@@ -26,8 +25,8 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.EventQueue;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -92,7 +91,7 @@ public abstract class RecurringAction extends AliasAction
|
||||
*/
|
||||
private void performThreadedAction(final Alias alias, final IMessage message)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.execute(() -> performAction(alias, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.audio;
|
||||
|
||||
@@ -25,13 +22,13 @@ import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
|
||||
*/
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
|
||||
Listener<ReusableFloatBuffer>
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IRealBufferListener,
|
||||
Listener<float[]>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||
private static float[] sHighPassFilterCoefficients;
|
||||
@@ -73,7 +70,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
}
|
||||
|
||||
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
|
||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
|
||||
@@ -120,22 +117,17 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer audioBuffer)
|
||||
public void receive(float[] audioBuffer)
|
||||
{
|
||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
ReusableFloatBuffer highPassFilteredAudio = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(highPassFilteredAudio.getSamplesCopy());
|
||||
highPassFilteredAudio.decrementUserCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
audioBuffer.decrementUserCount();
|
||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
public Listener<float[]> getBufferListener()
|
||||
{
|
||||
//Redirect received reusable buffers to the receive(buffer) method
|
||||
return this;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 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
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.audio.broadcast.icecast;
|
||||
|
||||
@@ -100,7 +97,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
final String metadataUpdateURL = sb.toString();
|
||||
URI uri = URI.create(metadataUpdateURL);
|
||||
|
||||
ThreadPool.SCHEDULED.submit(new Runnable()
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
|
||||
@@ -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.audio.broadcast.icecast;
|
||||
|
||||
@@ -45,7 +42,6 @@ import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
@@ -164,8 +160,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -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.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
@@ -167,7 +167,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -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.shoutcast.v1;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
@@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
@@ -179,7 +178,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.audio.broadcast.shoutcast.v1;
|
||||
|
||||
@@ -53,7 +50,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||
@@ -138,7 +134,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
|
||||
if(updateRequest != null)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(new Runnable()
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@@ -176,7 +172,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0l, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
//Fetch next metadata update to send
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.audio.broadcast.shoutcast.v2;
|
||||
|
||||
@@ -67,7 +64,6 @@ import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
|
||||
@@ -225,7 +221,7 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -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.audio.convert.thumbdv;
|
||||
@@ -36,8 +33,6 @@ import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechRes
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -82,11 +77,10 @@ public class ThumbDv implements AutoCloseable
|
||||
private ScheduledFuture mAudioDecodeProcessorHandle;
|
||||
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
|
||||
private AudioProtocol mAudioProtocol;
|
||||
private Listener<ReusableFloatBuffer> mAudioBufferListener;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ThumbDv");
|
||||
private Listener<float[]> mAudioBufferListener;
|
||||
private boolean mStarted;
|
||||
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<ReusableFloatBuffer> listener)
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<float[]> listener)
|
||||
{
|
||||
mAudioProtocol = audioProtocol;
|
||||
mAudioBufferListener = listener;
|
||||
@@ -209,8 +203,7 @@ public class ThumbDv implements AutoCloseable
|
||||
if(message instanceof DecodeSpeechResponse && mAudioBufferListener != null)
|
||||
{
|
||||
float[] samples = ((DecodeSpeechResponse)message).getSamples();
|
||||
ReusableFloatBuffer audioBuffer = mReusableBufferQueue.getBuffer(samples, System.currentTimeMillis());
|
||||
mAudioBufferListener.receive(audioBuffer);
|
||||
mAudioBufferListener.receive(samples);
|
||||
}
|
||||
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
|
||||
{
|
||||
@@ -456,9 +449,8 @@ public class ThumbDv implements AutoCloseable
|
||||
|
||||
mLog.debug("Starting thumb dv thread(s)");
|
||||
|
||||
final Listener<ReusableFloatBuffer> listener = reusableAudioPacket -> {
|
||||
final Listener<float[]> listener = packet -> {
|
||||
mLog.info("Got an audio packet!");
|
||||
reusableAudioPacket.decrementUserCount();
|
||||
};
|
||||
|
||||
try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener))
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.audio.invert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.FloatFIRFilter;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Based on the AudioType applied, produces normal, muted, or audio invert
|
||||
@@ -30,7 +31,7 @@ import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
public class AudioAdapter
|
||||
{
|
||||
private static int mGain = 5;
|
||||
private IOscillator mSineWaveGenerator;
|
||||
private IRealOscillator mSineWaveGenerator;
|
||||
private FloatFIRFilter mPostInversionFilter;
|
||||
private FloatFIRFilter mAudioHighPassFilter;
|
||||
private AudioType mAudioType = AudioType.NORMAL;
|
||||
@@ -39,7 +40,7 @@ public class AudioAdapter
|
||||
public AudioAdapter( int sampleRate )
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mSineWaveGenerator = new Oscillator( 3000, mSampleRate );
|
||||
mSineWaveGenerator = OscillatorFactory.getRealOscillator(3000, mSampleRate );
|
||||
mAudioHighPassFilter = new FloatFIRFilter(
|
||||
FilterFactory.getHighPass( mSampleRate, 200, 1600, 48,
|
||||
WindowType.HAMMING, true ), mGain );
|
||||
@@ -58,7 +59,7 @@ public class AudioAdapter
|
||||
|
||||
if( inversionFrequency != 0 )
|
||||
{
|
||||
mSineWaveGenerator.setFrequency( inversionFrequency );;
|
||||
mSineWaveGenerator.setFrequency( inversionFrequency );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,9 +90,7 @@ public class AudioAdapter
|
||||
default:
|
||||
retVal = (float)( mPostInversionFilter.get(
|
||||
mAudioHighPassFilter.get( sample ) *
|
||||
mSineWaveGenerator.inphase() ) );
|
||||
|
||||
mSineWaveGenerator.rotate();
|
||||
mSineWaveGenerator.generate(1)[0] ) );
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.audio.invert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.FloatFIRFilter;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
/**
|
||||
@@ -29,17 +29,33 @@ import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
*/
|
||||
public class AudioInverter implements RealSampleListener
|
||||
{
|
||||
private IOscillator mSineWaveGenerator;
|
||||
private static final float[] LOW_PASS_FILTER = new float[] { -0.0052419787903715655f,
|
||||
-0.0003789909443307852f, 0.0012970137854134386f, 0.004210460544067679f,
|
||||
0.008221266221665309f, 0.012951576579356689f, 0.017794312853913318f,
|
||||
0.021959871931790928f, 0.024583800118549094f, 0.024878144443784132f,
|
||||
0.022296319647647538f, 0.016685495012933472f, 0.008390268267799136f,
|
||||
-0.001718264469387386f, -0.012288389660616603f, -0.02161633130497537f,
|
||||
-0.027882128567957774f, -0.02943377233328899f, -0.025006516193566884f,
|
||||
-0.014040074880106284f, 0.0032217508351258103f, 0.025660031586092195f,
|
||||
0.05136009984280478f, 0.0778336106888181f, 0.10230926486919244f,
|
||||
0.12209961841343948f, 0.13496813439824185f, 0.13943117253534673f,
|
||||
0.13496813439824185f, 0.12209961841343948f, 0.10230926486919244f,
|
||||
0.0778336106888181f, 0.05136009984280478f, 0.025660031586092195f,
|
||||
0.0032217508351258103f, -0.014040074880106284f, -0.025006516193566884f,
|
||||
-0.02943377233328899f, -0.027882128567957774f, -0.02161633130497537f,
|
||||
-0.012288389660616603f, -0.001718264469387386f, 0.008390268267799136f,
|
||||
0.016685495012933472f, 0.022296319647647538f, 0.024878144443784132f,
|
||||
0.024583800118549094f, 0.021959871931790928f, 0.017794312853913318f,
|
||||
0.012951576579356689f, 0.008221266221665309f, 0.004210460544067679f,
|
||||
0.0012970137854134386f, -0.0003789909443307852f, -0.0052419787903715655f };
|
||||
private IRealOscillator mSineWaveGenerator;
|
||||
private FloatFIRFilter mPostInversionLowPassFilter;
|
||||
private RealSampleListener mListener;
|
||||
|
||||
public AudioInverter( int inversionFrequency, int sampleRate )
|
||||
{
|
||||
mSineWaveGenerator = new Oscillator( inversionFrequency, sampleRate );
|
||||
|
||||
mPostInversionLowPassFilter = new FloatFIRFilter(
|
||||
Filters.FIRLP_55TAP_48000FS_3000FC.getCoefficients(), 1.04f );
|
||||
|
||||
mSineWaveGenerator = OscillatorFactory.getRealOscillator( inversionFrequency, sampleRate );
|
||||
mPostInversionLowPassFilter = new FloatFIRFilter(LOW_PASS_FILTER, 1.04f );
|
||||
mPostInversionLowPassFilter.setListener( new FilteredSampleProcessor() );
|
||||
}
|
||||
|
||||
@@ -60,10 +76,7 @@ public class AudioInverter implements RealSampleListener
|
||||
//the low pass filter
|
||||
if( mPostInversionLowPassFilter != null )
|
||||
{
|
||||
mPostInversionLowPassFilter.receive(
|
||||
sample * mSineWaveGenerator.inphase() );
|
||||
|
||||
mSineWaveGenerator.rotate();
|
||||
mPostInversionLowPassFilter.receive(sample * mSineWaveGenerator.generate(1)[0] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
154
src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java
Normal file
154
src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer sample array wrapper class that provides access to a stream of either interleaved or
|
||||
* non-interleaved complex sample buffers converted from the raw byte sample array.
|
||||
*/
|
||||
public class ByteNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private static final int FRAGMENT_SIZE = 2048;
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
private float mAverageDc;
|
||||
|
||||
//Creates a static lookup table that converts the 8-bit valued range from 0 - 255 into scaled float values
|
||||
//of -1.0 to 0 to 1.0
|
||||
static
|
||||
{
|
||||
LOOKUP_VALUES = new float[256];
|
||||
|
||||
for(int x = 0; x < 256; x++)
|
||||
{
|
||||
LOOKUP_VALUES[x] = (float)(x - 127) / 128.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] mSamples;
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to process
|
||||
* @param timestamp of the samples
|
||||
* @param averageDc measured from sample stream
|
||||
*/
|
||||
public ByteNativeBuffer(byte[] samples, long timestamp, float averageDc)
|
||||
{
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
mAverageDc = averageDc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return new ComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return new InterleavedComplexSamplesIterator();
|
||||
}
|
||||
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
float[] i = new float[FRAGMENT_SIZE];
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
int samplesOffset = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < i.length; pointer++)
|
||||
{
|
||||
i[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mAverageDc;
|
||||
q[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesOffset;
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator of interleaved complex samples over the native byte buffer array.
|
||||
*/
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
float[] converted = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
int samplesPointer = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < converted.length; pointer++)
|
||||
{
|
||||
converted[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesPointer++])] - mAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesPointer;
|
||||
|
||||
return new InterleavedComplexSamples(converted, mTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating ByteNativeBuffer instances
|
||||
*/
|
||||
public class ByteNativeBufferFactory implements INativeBufferFactory
|
||||
{
|
||||
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
|
||||
private float mAverageDc = 0.0f;
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
|
||||
{
|
||||
byte[] copy = new byte[samples.capacity()];
|
||||
samples.get(copy);
|
||||
calculateDc(copy);
|
||||
return new ByteNativeBuffer(copy, timestamp, mAverageDc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
|
||||
* native buffer is used.
|
||||
* @param samples containing DC offset
|
||||
*/
|
||||
private void calculateDc(byte[] samples)
|
||||
{
|
||||
double dcAccumulator = 0;
|
||||
|
||||
for(byte sample: samples)
|
||||
{
|
||||
dcAccumulator += (sample & 0xFF);
|
||||
}
|
||||
|
||||
dcAccumulator /= samples.length;
|
||||
dcAccumulator -= 127.0f;
|
||||
dcAccumulator /= 128.0f;
|
||||
dcAccumulator -= mAverageDc;
|
||||
mAverageDc += (dcAccumulator * DC_FILTER_GAIN);
|
||||
}
|
||||
}
|
||||
145
src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java
Normal file
145
src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.SampleUtils;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Simple native buffer implementation that simply wraps a single, existing, non-native sample buffer.
|
||||
*/
|
||||
public class FloatNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private float[] mInterleavedComplexSamples;
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param complexSamples to wrap
|
||||
* @param timestamp for the buffer
|
||||
*/
|
||||
public FloatNativeBuffer(float[] complexSamples, long timestamp)
|
||||
{
|
||||
mInterleavedComplexSamples = complexSamples;
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param complexSamples to wrap
|
||||
*/
|
||||
public FloatNativeBuffer(float[] complexSamples)
|
||||
{
|
||||
this(complexSamples, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to wrap
|
||||
* @param timestamp for the buffer
|
||||
*/
|
||||
public FloatNativeBuffer(ComplexSamples samples, long timestamp)
|
||||
{
|
||||
this(SampleUtils.interleave(samples), timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to wrap
|
||||
*/
|
||||
public FloatNativeBuffer(InterleavedComplexSamples samples)
|
||||
{
|
||||
this(samples.samples(), samples.timestamp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return new ComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return new InterleavedComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mInterleavedComplexSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return SampleUtils.deinterleave(mInterleavedComplexSamples);
|
||||
}
|
||||
}
|
||||
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return new InterleavedComplexSamples(mInterleavedComplexSamples, mTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/main/java/io/github/dsheirer/buffer/INativeBuffer.java
Normal file
53
src/main/java/io/github/dsheirer/buffer/INativeBuffer.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A buffer of raw samples copied from native (ie outside of the JVM) memory into the JVM heap and
|
||||
* convertible to an iterable stream of interleaved or non-interleaved sample instances.
|
||||
*/
|
||||
public interface INativeBuffer
|
||||
{
|
||||
/**
|
||||
* Provides an iterator that converts the raw samples to complex samples format with separate (non-interleaved) I and Q sample arrays.
|
||||
*/
|
||||
Iterator<ComplexSamples> iterator();
|
||||
|
||||
/**
|
||||
* Provides an iterator that converts the raw samples to interleaved complex samples format.
|
||||
*/
|
||||
Iterator<InterleavedComplexSamples> iteratorInterleaved();
|
||||
|
||||
/**
|
||||
* Total number of complex samples for this buffer
|
||||
*/
|
||||
int sampleCount();
|
||||
|
||||
/**
|
||||
* Timestamp for this buffer
|
||||
* @return millis since epoch
|
||||
*/
|
||||
long getTimestamp();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Factory for creating native buffer wrapper instances
|
||||
*/
|
||||
public interface INativeBufferFactory
|
||||
{
|
||||
/**
|
||||
* Create a native buffer implementation that wraps the samples argument.
|
||||
*
|
||||
* Note: implementations of this factory should treat the samples argument as read-only and only make data copies.
|
||||
*
|
||||
* @param samples byte array copied from native memory
|
||||
* @param timestamp of the samples
|
||||
* @return instance
|
||||
*/
|
||||
INativeBuffer getBuffer(ByteBuffer samples, long timestamp);
|
||||
}
|
||||
@@ -16,27 +16,27 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Interface for a decimation filter for float array sample buffers
|
||||
* Interface for a provider of interleaved complex samples
|
||||
*/
|
||||
public interface IComplexDecimationFilter
|
||||
public interface INativeBufferProvider
|
||||
{
|
||||
/**
|
||||
* Decimates an array of float complex samples
|
||||
* @param samples to decimate
|
||||
* @return decimated samples
|
||||
* Adds the listener to receive buffers
|
||||
*/
|
||||
float[] decimateComplex(float[] samples);
|
||||
void addBufferListener(Listener<INativeBuffer> listener);
|
||||
|
||||
/**
|
||||
* Decimates an buffer of float complex samples
|
||||
* @param buffer to decimate
|
||||
* @return decimated samples
|
||||
* Removes the listener from receiving buffers
|
||||
*/
|
||||
ReusableComplexBuffer decimate(ReusableComplexBuffer buffer);
|
||||
void removeBufferListener(Listener<INativeBuffer> listener);
|
||||
|
||||
/**
|
||||
* Indicates if there are any buffer listeners registered
|
||||
*/
|
||||
boolean hasBufferListeners();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Poison pill implementation for use with Dispatcher.
|
||||
*/
|
||||
public class NativeBufferPoisonPill implements INativeBuffer
|
||||
{
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.sample.buffer;
|
||||
* 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.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -22,7 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
|
||||
/**
|
||||
* Delay buffer/queue for reusable complex buffers with support for pre-loading new buffer listeners (ie channels)
|
||||
* Delay buffer/queue with support for pre-loading new listeners (ie channels)
|
||||
* with delayed sample buffers and broadcasting of incoming sample buffers to a list of listeners.
|
||||
*
|
||||
* This class is designed to compensate/account for any delays in processing of a control channel and subsequent
|
||||
@@ -39,15 +43,13 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
* so as not to delay the stream of sample buffers. Channel listeners are expected to implement buffer queue processing
|
||||
* on another thread.
|
||||
*/
|
||||
public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffer>
|
||||
public class NativeSampleDelayBuffer implements Listener<INativeBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexDelayBuffer.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(NativeSampleDelayBuffer.class);
|
||||
|
||||
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBroadcaster = new ReusableBufferBroadcaster<>();
|
||||
private LinkedTransferQueue<ActionRequest> mActionRequestQueue = new LinkedTransferQueue<>();
|
||||
private ActionRequest mActionRequest;
|
||||
|
||||
private ReusableComplexBuffer[] mDelayBuffer;
|
||||
private Broadcaster<INativeBuffer> mBroadcaster = new Broadcaster<>();
|
||||
private LinkedTransferQueue<ActionRequest> mActionQueue = new LinkedTransferQueue<>();
|
||||
private INativeBuffer[] mDelayBuffer;
|
||||
private int mDelayBufferPointer = 0;
|
||||
private long mBufferDuration;
|
||||
|
||||
@@ -57,9 +59,9 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* @param size of the delay queue
|
||||
* @param bufferDuration in milliseconds for each complex buffer processed by this instance
|
||||
*/
|
||||
public ReusableComplexDelayBuffer(int size, long bufferDuration)
|
||||
public NativeSampleDelayBuffer(int size, long bufferDuration)
|
||||
{
|
||||
mDelayBuffer = new ReusableComplexBuffer[size];
|
||||
mDelayBuffer = new INativeBuffer[size];
|
||||
mBufferDuration = bufferDuration;
|
||||
}
|
||||
|
||||
@@ -69,12 +71,13 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
public void dispose()
|
||||
{
|
||||
clearBuffer();
|
||||
mBroadcaster.clear();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
//Submit a clear buffer request to be processed upon the next buffer that arrives
|
||||
mActionRequestQueue.offer(new ActionRequest());
|
||||
mActionQueue.offer(new ActionRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,59 +87,42 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
{
|
||||
for(int x = 0; x < mDelayBuffer.length; x++)
|
||||
{
|
||||
if(mDelayBuffer[x] != null)
|
||||
{
|
||||
mDelayBuffer[x].decrementUserCount();
|
||||
mDelayBuffer[x] = null;
|
||||
}
|
||||
mDelayBuffer[x] = null;
|
||||
}
|
||||
|
||||
mDelayBufferPointer = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
public synchronized void receive(INativeBuffer samples)
|
||||
{
|
||||
mActionRequest = mActionRequestQueue.poll();
|
||||
ActionRequest actionRequest = mActionQueue.poll();
|
||||
|
||||
while(mActionRequest != null)
|
||||
while(actionRequest != null)
|
||||
{
|
||||
switch(mActionRequest.getAction())
|
||||
switch(actionRequest.getAction())
|
||||
{
|
||||
case ADD_USER:
|
||||
processNewListener(mActionRequest);
|
||||
case ADD_LISTENER:
|
||||
processNewListener(actionRequest);
|
||||
break;
|
||||
case REMOVE_USER:
|
||||
mBroadcaster.removeListener(mActionRequest.getListener());
|
||||
case REMOVE_LISTENER:
|
||||
mBroadcaster.removeListener(actionRequest.getListener());
|
||||
break;
|
||||
case CLEAR_BUFFER:
|
||||
clearBuffer();
|
||||
break;
|
||||
}
|
||||
|
||||
mActionRequest = mActionRequestQueue.poll();
|
||||
actionRequest = mActionQueue.poll();
|
||||
}
|
||||
|
||||
//Increment user count before we hand it to the broadcaster so that we can keep it as a copy for the
|
||||
//delay buffer
|
||||
reusableComplexBuffer.incrementUserCount();
|
||||
|
||||
mBroadcaster.receive(reusableComplexBuffer);
|
||||
|
||||
//Decrement the user count on the oldest buffer before we replace it in the delay queue
|
||||
if(mDelayBuffer[mDelayBufferPointer] != null)
|
||||
{
|
||||
mDelayBuffer[mDelayBufferPointer].decrementUserCount();
|
||||
}
|
||||
mBroadcaster.receive(samples);
|
||||
|
||||
//Store the new buffer in the delay queue and increment the pointer
|
||||
mDelayBuffer[mDelayBufferPointer++] = reusableComplexBuffer;
|
||||
mDelayBuffer[mDelayBufferPointer++] = samples;
|
||||
|
||||
//Wrap the delay buffer pointer as needed
|
||||
if(mDelayBufferPointer >= mDelayBuffer.length)
|
||||
{
|
||||
mDelayBufferPointer = 0;
|
||||
}
|
||||
mDelayBufferPointer %= mDelayBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,23 +139,18 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
*/
|
||||
private void processNewListener(ActionRequest listenerToAdd)
|
||||
{
|
||||
ReusableComplexBuffer bufferToEvaluate;
|
||||
INativeBuffer toEvaluate;
|
||||
|
||||
int pointer = mDelayBufferPointer;
|
||||
|
||||
int preloadedBufferCount = 0;
|
||||
|
||||
for(int x = 0; x < mDelayBuffer.length; x++)
|
||||
{
|
||||
bufferToEvaluate = mDelayBuffer[pointer];
|
||||
toEvaluate = mDelayBuffer[pointer];
|
||||
|
||||
if(bufferToEvaluate != null &&
|
||||
(bufferToEvaluate.getTimestamp() >= listenerToAdd.getTimestamp() ||
|
||||
bufferToEvaluate.getTimestamp() + mBufferDuration >= listenerToAdd.getTimestamp()))
|
||||
if(toEvaluate != null &&
|
||||
((toEvaluate.getTimestamp() + mBufferDuration) >= listenerToAdd.getTimestamp()))
|
||||
{
|
||||
bufferToEvaluate.incrementUserCount();
|
||||
listenerToAdd.getListener().receive(bufferToEvaluate);
|
||||
preloadedBufferCount++;
|
||||
listenerToAdd.getListener().receive(toEvaluate);
|
||||
}
|
||||
|
||||
pointer++;
|
||||
@@ -190,28 +171,28 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* @param listener to add
|
||||
* @param timestamp of the oldest sample buffers to preload to the listener
|
||||
*/
|
||||
public void addListener(Listener<ReusableComplexBuffer> listener, long timestamp)
|
||||
public void addListener(Listener<INativeBuffer> listener, long timestamp)
|
||||
{
|
||||
mActionRequestQueue.add(new ActionRequest(listener, timestamp));
|
||||
mActionQueue.add(new ActionRequest(listener, timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving sample buffers
|
||||
*/
|
||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||
public void removeListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
mActionRequestQueue.add(new ActionRequest(listener));
|
||||
mActionQueue.add(new ActionRequest(listener));
|
||||
}
|
||||
|
||||
private enum Action{ADD_USER, REMOVE_USER, CLEAR_BUFFER};
|
||||
private enum Action{ADD_LISTENER, REMOVE_LISTENER, CLEAR_BUFFER};
|
||||
|
||||
/**
|
||||
* Actions that must be completed on the incoming sample stream thread.
|
||||
*/
|
||||
public class ActionRequest
|
||||
public class ActionRequest<T extends INativeBuffer>
|
||||
{
|
||||
private Action mAction;
|
||||
private Listener<ReusableComplexBuffer> mListener;
|
||||
private Listener<T> mListener;
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
@@ -219,9 +200,9 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* @param listener to add
|
||||
* @param timestamp for the preloading buffers to the listener from the delay queue
|
||||
*/
|
||||
public ActionRequest(Listener<ReusableComplexBuffer> listener, long timestamp)
|
||||
public ActionRequest(Listener<T> listener, long timestamp)
|
||||
{
|
||||
mAction = Action.ADD_USER;
|
||||
mAction = Action.ADD_LISTENER;
|
||||
mListener = listener;
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
@@ -230,9 +211,9 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* Creates a remove listener request
|
||||
* @param listener
|
||||
*/
|
||||
public ActionRequest(Listener<ReusableComplexBuffer> listener)
|
||||
public ActionRequest(Listener<T> listener)
|
||||
{
|
||||
mAction = Action.REMOVE_USER;
|
||||
mAction = Action.REMOVE_LISTENER;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@@ -252,7 +233,7 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
/**
|
||||
* Listener to be added/removed
|
||||
*/
|
||||
public Listener<ReusableComplexBuffer> getListener()
|
||||
public Listener<T> getListener()
|
||||
{
|
||||
return mListener;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer sample array wrapper class that provides access to a stream of either interleaved or
|
||||
* non-interleaved complex sample buffers converted from the raw byte sample array.
|
||||
*/
|
||||
public class SignedByteNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private static final int FRAGMENT_SIZE = 2048;
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
|
||||
//Creates a static lookup table that converts the signed byte values as:
|
||||
// Index 0 - 127: 0.0f to 1.0f
|
||||
// Index 128 - 255: -1.0f to -0.0f
|
||||
static
|
||||
{
|
||||
LOOKUP_VALUES = new float[256];
|
||||
|
||||
for(int x = 0; x < 256; x++)
|
||||
{
|
||||
LOOKUP_VALUES[x] = (float)((byte)x) / 128.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] mSamples;
|
||||
private long mTimestamp;
|
||||
private float mIAverageDc;
|
||||
private float mQAverageDc;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to process
|
||||
* @param timestamp of the samples
|
||||
* @param iAverageDc of the sample stream
|
||||
* @param qAverageDc of the sample stream
|
||||
*/
|
||||
public SignedByteNativeBuffer(byte[] samples, long timestamp, float iAverageDc, float qAverageDc)
|
||||
{
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
mIAverageDc = iAverageDc;
|
||||
mQAverageDc = qAverageDc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return new ComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return new InterleavedComplexSamplesIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator of complex samples over the native byte buffer array
|
||||
*/
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
float[] i = new float[FRAGMENT_SIZE];
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
int samplesOffset = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < i.length; pointer++)
|
||||
{
|
||||
i[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mIAverageDc;
|
||||
q[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mQAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesOffset;
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator of interleaved complex samples over the native byte buffer array.
|
||||
*/
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
float[] converted = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
int samplesOffset = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < converted.length; pointer += 2)
|
||||
{
|
||||
converted[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mIAverageDc;
|
||||
converted[pointer + 1] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mQAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesOffset;
|
||||
|
||||
return new InterleavedComplexSamples(converted, mTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating SignedByteNativeBuffer instances
|
||||
*/
|
||||
public class SignedByteNativeBufferFactory implements INativeBufferFactory
|
||||
{
|
||||
private static final float DC_FILTER_GAIN = 0.02f; //Normalizes DC over period of ~50 (1 / .02) buffers
|
||||
private float mIAverageDc = 0.0f;
|
||||
private float mQAverageDc = 0.0f;
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
|
||||
{
|
||||
byte[] copy = new byte[samples.capacity()];
|
||||
samples.get(copy);
|
||||
calculateDc(copy);
|
||||
return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
|
||||
* native buffer is used.
|
||||
* @param samples containing DC offset
|
||||
*/
|
||||
private void calculateDc(byte[] samples)
|
||||
{
|
||||
double iDcAccumulator = 0;
|
||||
double qDcAccumulator = 0;
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
iDcAccumulator += samples[x];
|
||||
qDcAccumulator += samples[x + 1];
|
||||
}
|
||||
|
||||
iDcAccumulator /= (samples.length / 2);
|
||||
iDcAccumulator /= 128.0f;
|
||||
iDcAccumulator -= mIAverageDc;
|
||||
mIAverageDc += (iDcAccumulator * DC_FILTER_GAIN);
|
||||
|
||||
qDcAccumulator /= (samples.length / 2);
|
||||
qDcAccumulator /= 128.0f;
|
||||
qDcAccumulator -= mQAverageDc;
|
||||
mQAverageDc += (qDcAccumulator * DC_FILTER_GAIN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Base complex samples iterator for raw Airspy sample buffers. Incorporates a Hilbert transform filter
|
||||
* for converting the real sample array to complex samples.
|
||||
* @param <T> either ComplexSamples or InterleavedComplexSamples
|
||||
*/
|
||||
public abstract class AirspyBufferIterator<T> implements Iterator<T>
|
||||
{
|
||||
protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
|
||||
protected static final int FRAGMENT_SIZE = 2048;
|
||||
protected static final float[] COEFFICIENTS =
|
||||
HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);
|
||||
|
||||
public static final int I_OVERLAP = 11;
|
||||
public static final int Q_OVERLAP = 23;
|
||||
|
||||
protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP];
|
||||
protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP];
|
||||
protected short[] mSamples;
|
||||
protected int mSamplesPointer = 0;
|
||||
protected long mTimestamp;
|
||||
protected float mAverageDc;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples from the airspy.
|
||||
* @param residualI samples from previous buffer
|
||||
* @param residualQ samples from previous buffer
|
||||
* @param averageDc as measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP)
|
||||
{
|
||||
throw new IllegalArgumentException("Residual I length [" + residualI.length +
|
||||
"] must be " + I_OVERLAP + " and Residual Q length [" + residualQ.length +
|
||||
"] must be " + Q_OVERLAP);
|
||||
}
|
||||
|
||||
int requiredInterval = FRAGMENT_SIZE * 2; //requires 4 bytes (2 samples) per fragment
|
||||
|
||||
if(samples.length % requiredInterval != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples short array length [" + mSamples.length +
|
||||
"]must be an integer multiple of " + requiredInterval);
|
||||
}
|
||||
|
||||
mAverageDc = averageDc;
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
|
||||
//Transfer and scale the residual I & Q samples from the previous buffer
|
||||
for(int i = 0; i < residualI.length; i++)
|
||||
{
|
||||
mIBuffer[i] = scale(residualI[i], averageDc);
|
||||
}
|
||||
|
||||
for(int q = 0; q < residualQ.length; q++)
|
||||
{
|
||||
mQBuffer[q] = scale(residualQ[q], averageDc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the short sample value to a 12-bit floating point and subtracts the average DC offset
|
||||
* @param value to scale
|
||||
* @param averageDc detected
|
||||
* @return scaled and normalized sample
|
||||
*/
|
||||
public static float scale(short value, float averageDc)
|
||||
{
|
||||
return ((float)value - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT - averageDc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Scalar implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorScalar extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = 0;
|
||||
|
||||
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
|
||||
{
|
||||
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float[] mFilterPart2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
|
||||
mFilterPart2 = new float[16];
|
||||
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector64Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
|
||||
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
|
||||
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
|
||||
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
|
||||
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
|
||||
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
|
||||
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
/**
|
||||
* Scalar implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = 0;
|
||||
|
||||
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
|
||||
{
|
||||
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float[] mFilterPart2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
|
||||
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
|
||||
mFilterPart2 = new float[16];
|
||||
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
|
||||
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
|
||||
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
|
||||
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
|
||||
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
|
||||
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
|
||||
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer scalar implementation for Airspy non-packed samples.
|
||||
*/
|
||||
public class AirspyNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private short[] mSamples;
|
||||
private short[] mResidualI;
|
||||
private short[] mResidualQ;
|
||||
private float mAverageDc;
|
||||
private long mTimestamp;
|
||||
private Implementation mInterleavedImplementation;
|
||||
private Implementation mNonInterleavedImplementation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples (non-packed) from the airspy device
|
||||
* @param residualI samples from previous buffer
|
||||
* @param residualQ samples from previous buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param interleavedImplementation optimal, scalar vs vector SIMD
|
||||
* @param nonInterleavedImplementation optimal, scalar vs vector SIMD
|
||||
*/
|
||||
public AirspyNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, Implementation interleavedImplementation,
|
||||
Implementation nonInterleavedImplementation)
|
||||
{
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % AirspyBufferIterator.FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples short[] length [" + samples.length +
|
||||
"] must be an even multiple of " + AirspyBufferIterator.FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mResidualI = residualI;
|
||||
mResidualQ = residualQ;
|
||||
mAverageDc = averageDc;
|
||||
mTimestamp = timestamp;
|
||||
mInterleavedImplementation = interleavedImplementation;
|
||||
mNonInterleavedImplementation = nonInterleavedImplementation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return switch(mInterleavedImplementation)
|
||||
{
|
||||
case VECTOR_SIMD_512 -> new AirspyBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_256-> new AirspyBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_128-> new AirspyBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_64 ->new AirspyBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
default -> new AirspyBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return switch(mInterleavedImplementation)
|
||||
{
|
||||
case VECTOR_SIMD_512 -> new AirspyInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_256-> new AirspyInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_128-> new AirspyInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_64 ->new AirspyInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
default -> new AirspyInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating SignedByteNativeBuffer instances
|
||||
*/
|
||||
public class AirspyNativeBufferFactory implements INativeBufferFactory
|
||||
{
|
||||
private boolean mSamplePacking = false;
|
||||
private short[] mResidualI = new short[AirspyBufferIterator.I_OVERLAP];
|
||||
private short[] mResidualQ = new short[AirspyBufferIterator.Q_OVERLAP];
|
||||
private IAirspySampleConverter mConverter;
|
||||
private Implementation mInterleavedIteratorImplementation;
|
||||
private Implementation mNonInterleavedIteratorImplementation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public AirspyNativeBufferFactory()
|
||||
{
|
||||
updateConverter();
|
||||
|
||||
mInterleavedIteratorImplementation = CalibrationManager.getInstance()
|
||||
.getImplementation(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR);
|
||||
mNonInterleavedIteratorImplementation = CalibrationManager.getInstance()
|
||||
.getImplementation(CalibrationType.AIRSPY_UNPACKED_ITERATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample packing places two 12-bit samples into 3 bytes when enabled or
|
||||
* places two 12-bit samples into 4 bytes when disabled.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setSamplePacking(boolean enabled)
|
||||
{
|
||||
mSamplePacking = enabled;
|
||||
updateConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an optimal sample converter instance.
|
||||
*/
|
||||
private void updateConverter()
|
||||
{
|
||||
if(mSamplePacking)
|
||||
{
|
||||
if(!(mConverter instanceof ScalarPackedSampleConverter))
|
||||
{
|
||||
mConverter = new ScalarPackedSampleConverter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AIRSPY_SAMPLE_CONVERTER);
|
||||
|
||||
if(implementation == Implementation.VECTOR_SIMD_PREFERRED)
|
||||
{
|
||||
if(!(mConverter instanceof VectorUnpackedSampleConverter))
|
||||
{
|
||||
mConverter = new VectorUnpackedSampleConverter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!(mConverter instanceof ScalarUnpackedSampleConverter))
|
||||
{
|
||||
mConverter = new ScalarUnpackedSampleConverter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer buffer, long timestamp)
|
||||
{
|
||||
short[] samples = mConverter.convert(buffer);
|
||||
|
||||
INativeBuffer nativeBuffer = new AirspyNativeBuffer(samples,
|
||||
Arrays.copyOf(mResidualI, mResidualI.length),
|
||||
Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp,
|
||||
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation);
|
||||
|
||||
extractResidual(samples);
|
||||
|
||||
return nativeBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the residual overlap samples needed for continuity in the Hilbert transform filter.
|
||||
* @param samples to extract residual from
|
||||
*/
|
||||
private void extractResidual(short[] samples)
|
||||
{
|
||||
int offset = samples.length - (AirspyBufferIterator.I_OVERLAP * 2);
|
||||
|
||||
for(int i = 0; i < AirspyBufferIterator.I_OVERLAP; i++)
|
||||
{
|
||||
mResidualI[i] = samples[offset + (2 * i)];
|
||||
}
|
||||
|
||||
offset = samples.length - (AirspyBufferIterator.Q_OVERLAP * 2) + 1;
|
||||
|
||||
for(int q = 0; q < AirspyBufferIterator.Q_OVERLAP; q++)
|
||||
{
|
||||
mResidualQ[q] = samples[offset + (2 * q)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Interface to convert airspy samples from a byte buffer to an array of shorts.
|
||||
*/
|
||||
public interface IAirspySampleConverter
|
||||
{
|
||||
/**
|
||||
* Converts the airspy byte samples contained in the byte buffer to their short-valued representation
|
||||
* @param buffer of airspy samples
|
||||
* @return converted samples
|
||||
*/
|
||||
short[] convert(ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* Current DC average of the samples that have been processed thus far.
|
||||
*/
|
||||
float getAverageDc();
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of airspy sample converter for un-packed samples.
|
||||
*/
|
||||
public class ScalarPackedSampleConverter implements IAirspySampleConverter
|
||||
{
|
||||
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
|
||||
private float mAverageDc = 0.0f;
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
int offset = 0;
|
||||
long dcAccumulator = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2, b3;
|
||||
|
||||
samples = new short[buffer.capacity() / 3 * 2];
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
b3 = buffer.get(offset++);
|
||||
|
||||
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
|
||||
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
averageDcNow -= mAverageDc;
|
||||
mAverageDc += (averageDcNow * DC_FILTER_GAIN);
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mAverageDc;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
byte[] raw = new byte[262144];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(raw);
|
||||
|
||||
ScalarPackedSampleConverter scalar = new ScalarPackedSampleConverter();
|
||||
VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter();
|
||||
|
||||
long accumulator = 0;
|
||||
int iterations = 1_000_000;
|
||||
|
||||
System.out.println("Starting ...");
|
||||
long start = System.currentTimeMillis();
|
||||
for(int count = 0; count < iterations; count++)
|
||||
{
|
||||
// short[] samples = scalar.convert(buffer);
|
||||
short[] samples = vector.convert(buffer);
|
||||
accumulator += samples[1];
|
||||
}
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println("Accumulator: " + accumulator);
|
||||
System.out.println("Elapsed: " + (elapsed / 1000.0f));
|
||||
System.out.println("Finished");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of airspy sample converter for un-packed samples.
|
||||
*/
|
||||
public class ScalarUnpackedSampleConverter implements IAirspySampleConverter
|
||||
{
|
||||
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
|
||||
private float mAverageDc = 0.0f;
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
int offset = 0;
|
||||
long dcAccumulator = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2;
|
||||
|
||||
samples = new short[buffer.capacity() / 2];
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
averageDcNow -= mAverageDc;
|
||||
mAverageDc += (averageDcNow * DC_FILTER_GAIN);
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mAverageDc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 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.buffer.airspy;
|
||||
|
||||
import jdk.incubator.vector.ShortVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of airspy sample converter for un-packed samples.
|
||||
*/
|
||||
public class VectorUnpackedSampleConverter implements IAirspySampleConverter
|
||||
{
|
||||
private static final VectorSpecies<Short> VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED;
|
||||
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
|
||||
private float mAverageDc = 0.0f;
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2;
|
||||
|
||||
samples = new short[buffer.capacity() / 2];
|
||||
|
||||
int bytesOffset;
|
||||
int rawPointer = 0;
|
||||
ShortVector vector;
|
||||
|
||||
int samplesOffset = 0;
|
||||
|
||||
short[] bytes1 = new short[VECTOR_SPECIES.length()];
|
||||
short[] bytes2 = new short[VECTOR_SPECIES.length()];
|
||||
|
||||
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
|
||||
{
|
||||
bytesOffset = 0;
|
||||
|
||||
while(bytesOffset < VECTOR_SPECIES.length())
|
||||
{
|
||||
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
|
||||
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
|
||||
}
|
||||
|
||||
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
|
||||
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
|
||||
dcAccumulator += vector.reduceLanes(VectorOperators.ADD);
|
||||
vector.intoArray(samples, samplesOffset);
|
||||
}
|
||||
|
||||
for(; samplesOffset < samples.length; samplesOffset++)
|
||||
{
|
||||
b1 = buffer.get(rawPointer++);
|
||||
b2 = buffer.get(rawPointer++);
|
||||
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[samplesOffset] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
averageDcNow -= mAverageDc;
|
||||
mAverageDc += (averageDcNow * DC_FILTER_GAIN);
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mAverageDc;
|
||||
}
|
||||
}
|
||||
@@ -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.channel.state;
|
||||
@@ -57,6 +54,9 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
|
||||
protected boolean mSourceOverflow = false;
|
||||
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
|
||||
|
||||
|
||||
//TODO: remove the IOverflowListener code from this class
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel configuration
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.controller;
|
||||
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
@@ -25,8 +44,7 @@ public class NamingThreadFactory implements ThreadFactory
|
||||
public NamingThreadFactory( String prefix )
|
||||
{
|
||||
mThreadGroup = Thread.currentThread().getThreadGroup();
|
||||
|
||||
mNamePrefix = prefix + " pool-" + mPoolNumber.getAndIncrement() + "-thread-";
|
||||
mNamePrefix = prefix + " thread ";
|
||||
}
|
||||
|
||||
public Thread newThread( Runnable runnable )
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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,17 +14,16 @@
|
||||
*
|
||||
* 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.afsk;
|
||||
|
||||
import io.github.dsheirer.bits.IBinarySymbolProcessor;
|
||||
import io.github.dsheirer.buffer.FloatAveragingBuffer;
|
||||
import io.github.dsheirer.dsp.filter.resample.RealResampler;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -39,7 +37,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Provides normal or inverted decoded output.
|
||||
*/
|
||||
public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
public class AFSK1200Decoder implements Listener<float[]>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
|
||||
|
||||
@@ -71,7 +69,7 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
private boolean mSampleDecision;
|
||||
|
||||
//Resample to an integral of the baud rate 1200 baud * 6 samples per symbol = 7200.0 Hertz
|
||||
private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 2000, 1);
|
||||
private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 8192, 512);
|
||||
|
||||
/**
|
||||
* Constructs a decoder using the provided arguments.
|
||||
@@ -115,7 +113,7 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
public void receive(float[] buffer)
|
||||
{
|
||||
mResampler.resample(buffer);
|
||||
}
|
||||
@@ -147,17 +145,15 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
}
|
||||
|
||||
|
||||
public class Decoder implements Listener<ReusableFloatBuffer>
|
||||
public class Decoder implements Listener<float[]>
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
public void receive(float[] buffer)
|
||||
{
|
||||
//Calculate correlation values against each 1200/1800 reference signal
|
||||
mCorrelationValuesMark = mCorrelatorMark.process(buffer);
|
||||
mCorrelationValuesSpace = mCorrelatorSpace.process(buffer);
|
||||
|
||||
buffer.decrementUserCount();
|
||||
|
||||
for(int x = 0; x < mCorrelationValuesMark.length; x++)
|
||||
{
|
||||
//1200 = Mark (1) and 1800 = Space (0)
|
||||
@@ -207,8 +203,8 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
{
|
||||
mAveragingBuffer = new FloatAveragingBuffer(averagingPeriod);
|
||||
|
||||
IOscillator referenceSignalGenerator = new Oscillator(frequency, sampleRate);
|
||||
mReferenceSamples = referenceSignalGenerator.generateReal(correlationPeriod);
|
||||
IRealOscillator referenceSignalGenerator = OscillatorFactory.getRealOscillator(frequency, sampleRate);
|
||||
mReferenceSamples = referenceSignalGenerator.generate(correlationPeriod);
|
||||
|
||||
mDemodulatedSamples = new float[correlationPeriod];
|
||||
}
|
||||
@@ -217,18 +213,16 @@ public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
* Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol
|
||||
* period to derive a correlation value that is in-turn averaged over one symbol period.
|
||||
*
|
||||
* @param reusableFloatBuffer containing FM demodulated samples
|
||||
* @param samples containing FM demodulated samples
|
||||
* @return a reusable array of correlation values for each sample
|
||||
*/
|
||||
public float[] process(ReusableFloatBuffer reusableFloatBuffer)
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
if(mCorrelationValues == null || mCorrelationValues.length != reusableFloatBuffer.getSampleCount())
|
||||
if(mCorrelationValues == null || mCorrelationValues.length != samples.length)
|
||||
{
|
||||
mCorrelationValues = new float[reusableFloatBuffer.getSampleCount()];
|
||||
mCorrelationValues = new float[samples.length];
|
||||
}
|
||||
|
||||
float[] samples = reusableFloatBuffer.getSamples();
|
||||
|
||||
int y;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.afsk;
|
||||
|
||||
import io.github.dsheirer.buffer.FloatAveragingBuffer;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class AFSKTest
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
|
||||
|
||||
private static final DecimalFormat df = new DecimalFormat("00.000");
|
||||
|
||||
public static String format(double value)
|
||||
{
|
||||
if(value > 0)
|
||||
{
|
||||
return " " + df.format(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return df.format(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static float[] generateAFSK1200(boolean[] symbols, int samplesPerSymbol, float gain)
|
||||
{
|
||||
float[] samples = new float[symbols.length * samplesPerSymbol];
|
||||
|
||||
Oscillator oscillator = new Oscillator(1200.0, (double)samplesPerSymbol * 1200);
|
||||
|
||||
for(int x = 0; x < symbols.length; x++)
|
||||
{
|
||||
oscillator.setFrequency(symbols[x] ? 1200.0 : 1800.0);
|
||||
|
||||
for(int y = 0; y < samplesPerSymbol; y++)
|
||||
{
|
||||
Complex sample = new Complex(oscillator.inphase(), oscillator.quadrature());
|
||||
sample.multiply(gain);
|
||||
samples[x * samplesPerSymbol + y] = sample.inphase();
|
||||
oscillator.rotate();
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int sampleSize = 6;
|
||||
|
||||
FloatAveragingBuffer avg1200 = new FloatAveragingBuffer(sampleSize);
|
||||
FloatAveragingBuffer avg1800 = new FloatAveragingBuffer(sampleSize);
|
||||
|
||||
IOscillator oscillator1200 = new Oscillator(1200.0, 7200.0);
|
||||
IOscillator oscillator1800 = new Oscillator(1800.0, 7200.0);
|
||||
oscillator1200.generateReal(1);
|
||||
oscillator1800.generateReal(1);
|
||||
|
||||
float[] reference1200 = oscillator1200.generateReal(sampleSize);
|
||||
float[] reference1800 = oscillator1800.generateReal(sampleSize);
|
||||
|
||||
boolean[] symbols = new boolean[]{true,false,true,false,true,false,true,false,true,true,true,false,true,false,
|
||||
false,false,true,false,false,false,false,true,false,true,true,true,true,false,true,true,false,true,false,
|
||||
true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,
|
||||
false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,
|
||||
true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,
|
||||
false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,
|
||||
true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,
|
||||
false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,
|
||||
true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,
|
||||
false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false};
|
||||
|
||||
float[] samples = generateAFSK1200(symbols, 6, 0.5f);
|
||||
|
||||
mLog.debug("Length:" + samples.length);
|
||||
|
||||
int offset = 0;
|
||||
|
||||
float correlation1200 = 0.0f;
|
||||
float correlation1800 = 0.0f;
|
||||
|
||||
while(offset + sampleSize < samples.length)
|
||||
{
|
||||
correlation1200 = 0.0f;
|
||||
correlation1800 = 0.0f;
|
||||
|
||||
for(int x = 0; x < sampleSize; x++)
|
||||
{
|
||||
correlation1200 += reference1200[x] * samples[x + offset];
|
||||
correlation1800 += reference1800[x] * samples[x + offset];
|
||||
}
|
||||
|
||||
correlation1200 = avg1200.get(FastMath.abs(correlation1200));
|
||||
correlation1800 = avg1800.get(FastMath.abs(correlation1800));
|
||||
|
||||
mLog.debug(offset + " 1200:" + format(correlation1200) + " 1800:" + format(correlation1800) +
|
||||
" Decision: " + (correlation1200 > correlation1800 ? "0" : "1"));
|
||||
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.dsp.am;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
*/
|
||||
public class AMDemodulator
|
||||
public class AMDemodulator implements IAmDemodulator
|
||||
{
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AMDemodulator");
|
||||
private int mOutputBufferPointer;
|
||||
private float mGain;
|
||||
|
||||
/**
|
||||
@@ -59,25 +57,15 @@ public class AMDemodulator
|
||||
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demodulates the reusable comples sample buffer and returns a demodulated audio buffer.
|
||||
* @param complexBuffer to demodulate
|
||||
* @return demodulated audio buffer.
|
||||
*/
|
||||
public ReusableFloatBuffer demodulate(ReusableComplexBuffer complexBuffer)
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
|
||||
float[] input = complexBuffer.getSamples();
|
||||
float[] output = reusableFloatBuffer.getSamples();
|
||||
mOutputBufferPointer = 0;
|
||||
float[] demodulated = new float[i.length];
|
||||
|
||||
for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
|
||||
for(int x = 0; x < i.length; x++)
|
||||
{
|
||||
output[mOutputBufferPointer++] = demodulate(input[x], input[x + 1]);
|
||||
demodulated[x] = (float)FastMath.sqrt((i[x] * i[x]) + (q[x] * q[x])) * mGain;
|
||||
}
|
||||
|
||||
complexBuffer.decrementUserCount();
|
||||
|
||||
return reusableFloatBuffer;
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
|
||||
15
src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java
Normal file
15
src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package io.github.dsheirer.dsp.am;
|
||||
|
||||
/**
|
||||
* AM Demodulator interface
|
||||
*/
|
||||
public interface IAmDemodulator
|
||||
{
|
||||
/**
|
||||
* Demodulate the complex I & Q sample arrays
|
||||
* @param i samples
|
||||
* @param q samples
|
||||
* @return demodulated samples.
|
||||
*/
|
||||
float[] demodulate(float[] i, float[] q);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 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
|
||||
@@ -18,10 +18,44 @@
|
||||
*/
|
||||
package io.github.dsheirer.dsp.filter;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter128Bit;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter256Bit;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter512Bit;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter64Bit;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilterDefaultBit;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange;
|
||||
import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap128Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap256Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap512Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter11Tap64Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter128Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap128Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap256Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap512Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter15Tap64Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap128Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap256Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap512Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter23Tap64Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter256Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter512Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap128Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap256Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap512Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter63Tap64Bit;
|
||||
import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter64Bit;
|
||||
import io.github.dsheirer.dsp.window.WindowFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
@@ -47,9 +81,9 @@ public class FilterFactory
|
||||
* @param frequency - cutoff in hertz
|
||||
* @param length - filter length
|
||||
* @param window - to apply against the coefficients
|
||||
* @return
|
||||
* @return coefficients
|
||||
*/
|
||||
public static float[] getSinc(double sampleRate, long frequency, int length, Window.WindowType window)
|
||||
public static float[] getSinc(double sampleRate, long frequency, int length, WindowType window)
|
||||
{
|
||||
int evenLength = length % 2 == 0 ? length : length + 1;
|
||||
|
||||
@@ -90,7 +124,7 @@ public class FilterFactory
|
||||
}
|
||||
|
||||
//Apply the window against the coefficients
|
||||
coefficients = Window.apply(window, coefficients);
|
||||
coefficients = WindowFactory.apply(window, coefficients);
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
@@ -223,7 +257,7 @@ public class FilterFactory
|
||||
* @param windowType - window to apply against the generated coefficients
|
||||
* @return
|
||||
*/
|
||||
public static float[] getLowPass(double sampleRate, long cutoff, int filterLength, Window.WindowType windowType)
|
||||
public static float[] getLowPass(double sampleRate, long cutoff, int filterLength, WindowType windowType)
|
||||
{
|
||||
return getSinc(sampleRate, cutoff, filterLength, windowType);
|
||||
}
|
||||
@@ -243,7 +277,7 @@ public class FilterFactory
|
||||
* - stopFrequency <= sampleRate/2
|
||||
*/
|
||||
public static float[] getLowPass(double sampleRate, int passFrequency, int stopFrequency, int attenuation,
|
||||
Window.WindowType windowType, boolean forceOddLength)
|
||||
WindowType windowType, boolean forceOddLength)
|
||||
{
|
||||
if(stopFrequency < passFrequency || stopFrequency > (sampleRate / 2))
|
||||
{
|
||||
@@ -276,7 +310,7 @@ public class FilterFactory
|
||||
* @param windowType - window to apply against the generated coefficients
|
||||
* @return
|
||||
*/
|
||||
public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, Window.WindowType windowType)
|
||||
public static float[] getHighPass(int sampleRate, long cutoff, int filterLength, WindowType windowType)
|
||||
{
|
||||
//Convert the high frequency cutoff to its low frequency cutoff
|
||||
//equivalent, so that when we generate the low pass filter, prior to
|
||||
@@ -287,7 +321,7 @@ public class FilterFactory
|
||||
}
|
||||
|
||||
public static float[] getHighPass(int sampleRate, long stopFrequency, long passFrequency, int attenuation,
|
||||
Window.WindowType windowType, boolean forceOddLength)
|
||||
WindowType windowType, boolean forceOddLength)
|
||||
{
|
||||
/* reverse the stop and pass frequency to get the low pass variant */
|
||||
int tapCount = getTapCount(sampleRate, stopFrequency, passFrequency, attenuation);
|
||||
@@ -508,7 +542,7 @@ public class FilterFactory
|
||||
* @param outputSampleRate
|
||||
* @return
|
||||
*/
|
||||
public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, Window.WindowType window)
|
||||
public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, WindowType window)
|
||||
{
|
||||
int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500,
|
||||
attenuation);
|
||||
@@ -760,7 +794,7 @@ public class FilterFactory
|
||||
double cutoff = (channelBandwidth * 1.10) / (channelSampleRate * (double)channels);
|
||||
|
||||
//Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer.
|
||||
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0);
|
||||
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0f);
|
||||
|
||||
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
|
||||
float[] extendedTaps = new float[taps.length + 1];
|
||||
@@ -819,7 +853,7 @@ public class FilterFactory
|
||||
//Get an initial filter and band edge frequency response using the channel bandwidth as a cutoff
|
||||
float[] taps = null;
|
||||
|
||||
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0);
|
||||
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0f);
|
||||
double response = FilterFactory.evaluate(taps, bandEdge);
|
||||
|
||||
//Set cutoff adjustment threshold - we'll test cutoff frequencies to around 1 hertz resolution
|
||||
@@ -832,7 +866,7 @@ public class FilterFactory
|
||||
if(matchesObjective(response, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE, MARGIN_OF_ERROR) &&
|
||||
(cutoffFrequency + increment <= bandEdge))
|
||||
{
|
||||
float[] higherCutoffTaps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency + increment, 80.0);
|
||||
float[] higherCutoffTaps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency + increment, 80.0f);
|
||||
double higherCutoffResponse = FilterFactory.evaluate(higherCutoffTaps, bandEdge);
|
||||
|
||||
if(matchesObjective(higherCutoffResponse, PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE, MARGIN_OF_ERROR))
|
||||
@@ -878,7 +912,7 @@ public class FilterFactory
|
||||
increment = cutoffFrequency * 0.1;
|
||||
}
|
||||
|
||||
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0);
|
||||
taps = FilterFactory.getKaiserSinc(filterLength, cutoffFrequency, 80.0f);
|
||||
response = FilterFactory.evaluate(taps, bandEdge);
|
||||
}
|
||||
}
|
||||
@@ -900,7 +934,7 @@ public class FilterFactory
|
||||
mLog.debug("Input Sample Rate: " + sampleRate);
|
||||
mLog.debug("Channel Bandwidth: " + channelBandwidth);
|
||||
mLog.debug("Channels: " + channels);
|
||||
mLog.debug("Window Type: " + Window.WindowType.KAISER.name());
|
||||
mLog.debug("Window Type: " + WindowType.KAISER.name());
|
||||
mLog.debug("Taps Per Channel - Requested:" + tapsPerChannel + " Actual:" + ((double)extendedTaps.length / (double)channels));
|
||||
mLog.debug("Filter Length: " + (extendedTaps.length));
|
||||
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
|
||||
@@ -928,7 +962,7 @@ public class FilterFactory
|
||||
* @return filter coefficients.
|
||||
* @throws FilterDesignException if the requested length is not odd
|
||||
*/
|
||||
public static float[] getSinc(double cutoff, int length, Window.WindowType windowType) throws FilterDesignException
|
||||
public static float[] getSinc(double cutoff, int length, WindowType windowType) throws FilterDesignException
|
||||
{
|
||||
if(length % 2 == 0)
|
||||
{
|
||||
@@ -938,7 +972,7 @@ public class FilterFactory
|
||||
float[] coefficients = new float[length];
|
||||
int half = length / 2;
|
||||
|
||||
double[] window = Window.getWindow(windowType, length);
|
||||
float[] window = WindowFactory.getWindow(windowType, length);
|
||||
|
||||
double scalor = 2.0 * cutoff;
|
||||
double piScalor = FastMath.PI * scalor;
|
||||
@@ -967,7 +1001,7 @@ public class FilterFactory
|
||||
* @return filter coefficients.
|
||||
* @throws FilterDesignException if the requested length is not odd
|
||||
*/
|
||||
public static float[] getKaiserSinc(int length, double cutoff, double attenuation) throws FilterDesignException
|
||||
public static float[] getKaiserSinc(int length, double cutoff, float attenuation) throws FilterDesignException
|
||||
{
|
||||
if(length % 2 == 0)
|
||||
{
|
||||
@@ -977,7 +1011,7 @@ public class FilterFactory
|
||||
float[] coefficients = new float[length];
|
||||
int half = length / 2;
|
||||
|
||||
double[] window = Window.getKaiser(length, attenuation);
|
||||
float[] window = WindowFactory.getKaiser(length, attenuation);
|
||||
|
||||
double scalor = 2.0 * cutoff;
|
||||
double piScalor = FastMath.PI * scalor;
|
||||
@@ -1004,7 +1038,7 @@ public class FilterFactory
|
||||
* @param windowType to apply to the filter.
|
||||
* @return coefficients
|
||||
*/
|
||||
public static float[] getHalfBand(int length, Window.WindowType windowType)
|
||||
public static float[] getHalfBand(int length, WindowType windowType)
|
||||
{
|
||||
if((length - 3) % 4 != 0)
|
||||
{
|
||||
@@ -1012,7 +1046,7 @@ public class FilterFactory
|
||||
"integer multiple in (N=4m+3), e.g. 7, 11, 15, etc");
|
||||
}
|
||||
|
||||
double[] window = Window.getWindow(windowType, length);
|
||||
float[] window = WindowFactory.getWindow(windowType, length);
|
||||
float[] taps = new float[length];
|
||||
|
||||
int halfLength = length / 2;
|
||||
@@ -1035,6 +1069,131 @@ public class FilterFactory
|
||||
return taps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the optimal FIR filter implementation using calibration data to select
|
||||
* from among the scalar and vector implementations.
|
||||
* @param coefficients for the filter
|
||||
* @return fir filter implementation
|
||||
*/
|
||||
public static IRealFilter getRealFilter(float[] coefficients)
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_FIR);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_PREFERRED:
|
||||
return new VectorRealFIRFilterDefaultBit(coefficients);
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorRealFIRFilter64Bit(coefficients);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorRealFIRFilter128Bit(coefficients);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorRealFIRFilter256Bit(coefficients);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorRealFIRFilter512Bit(coefficients);
|
||||
case UNCALIBRATED:
|
||||
case SCALAR:
|
||||
default:
|
||||
return new RealFIRFilter(coefficients);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the optimal decimation filter implementation for filter length and window type
|
||||
* using calibration data to select among scalar and vector implementation options.
|
||||
* @param length of decimation filter
|
||||
* @param windowType for designing the filter
|
||||
* @return filter implementation
|
||||
*/
|
||||
public static IRealDecimationFilter getRealDecimationFilter(int length, WindowType windowType)
|
||||
{
|
||||
float[] coefficients = getHalfBand(length, windowType);
|
||||
|
||||
switch(length)
|
||||
{
|
||||
case 11:
|
||||
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_11_TAP))
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorRealHalfBandDecimationFilter11Tap64Bit(coefficients);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorRealHalfBandDecimationFilter11Tap128Bit(coefficients);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorRealHalfBandDecimationFilter11Tap256Bit(coefficients);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorRealHalfBandDecimationFilter11Tap512Bit(coefficients);
|
||||
case SCALAR:
|
||||
case UNCALIBRATED:
|
||||
default:
|
||||
return new RealHalfBandDecimationFilter(coefficients);
|
||||
}
|
||||
case 15:
|
||||
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_15_TAP))
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorRealHalfBandDecimationFilter15Tap64Bit(coefficients);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorRealHalfBandDecimationFilter15Tap128Bit(coefficients);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorRealHalfBandDecimationFilter15Tap256Bit(coefficients);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorRealHalfBandDecimationFilter15Tap512Bit(coefficients);
|
||||
case SCALAR:
|
||||
case UNCALIBRATED:
|
||||
default:
|
||||
return new RealHalfBandDecimationFilter(coefficients);
|
||||
}
|
||||
case 23:
|
||||
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_23_TAP))
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorRealHalfBandDecimationFilter23Tap64Bit(coefficients);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorRealHalfBandDecimationFilter23Tap128Bit(coefficients);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorRealHalfBandDecimationFilter23Tap256Bit(coefficients);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients);
|
||||
case SCALAR:
|
||||
case UNCALIBRATED:
|
||||
default:
|
||||
return new RealHalfBandDecimationFilter(coefficients);
|
||||
}
|
||||
case 63:
|
||||
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_63_TAP))
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorRealHalfBandDecimationFilter63Tap512Bit(coefficients);
|
||||
case SCALAR:
|
||||
case UNCALIBRATED:
|
||||
default:
|
||||
return new RealHalfBandDecimationFilter(coefficients);
|
||||
}
|
||||
default:
|
||||
switch(CalibrationManager.getInstance().getImplementation(CalibrationType.FILTER_HALF_BAND_REAL_DEFAULT))
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorRealHalfBandDecimationFilter64Bit(coefficients);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorRealHalfBandDecimationFilter128Bit(coefficients);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorRealHalfBandDecimationFilter256Bit(coefficients);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorRealHalfBandDecimationFilter512Bit(coefficients);
|
||||
case SCALAR:
|
||||
case UNCALIBRATED:
|
||||
default:
|
||||
return new RealHalfBandDecimationFilter(coefficients);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two doubles for equals and avoids any rounding error that are present.
|
||||
*
|
||||
@@ -1053,7 +1212,7 @@ public class FilterFactory
|
||||
|
||||
int length = 15;
|
||||
|
||||
float [] taps = FilterFactory.getHalfBand(length, Window.WindowType.HAMMING);
|
||||
float [] taps = FilterFactory.getHalfBand(length, WindowType.HAMMING);
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,204 +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.dsp.filter;
|
||||
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class FloatHalfBandFilter implements RealSampleListener
|
||||
{
|
||||
private RealSampleListener mListener;
|
||||
private ArrayList<Float> mBuffer;
|
||||
private int mBufferSize = 1; //Temporary initial value
|
||||
private int mBufferPointer = 0;
|
||||
private float mGain;
|
||||
private float[] mCoefficients;
|
||||
private int[][] mIndexMap;
|
||||
private int mCenterCoefficient;
|
||||
private int mCenterCoefficientMapIndex;
|
||||
private boolean mDispatchFlag = false;
|
||||
|
||||
|
||||
/**
|
||||
* Half-Band filter with decimation by 2 against real valued floats.
|
||||
*
|
||||
* Takes advantage of the symmetrical nature of FIR filter coefficients by
|
||||
* adding oldest and newest sample first, then multiplying once by the
|
||||
* corresponding coefficient
|
||||
*
|
||||
* Also, takes advantage of the 0-valued FIR half-band coefficents inherent
|
||||
* in the half-band filter, and does not calculate those coefficients.
|
||||
*
|
||||
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
|
||||
*
|
||||
* @param filter - filter coefficients
|
||||
* @param gain - gain multiplier. Use 1.0 for unity/no gain
|
||||
*/
|
||||
public FloatHalfBandFilter( Filters filter, float gain )
|
||||
{
|
||||
mCoefficients = filter.getCoefficients();
|
||||
mBuffer = new ArrayList<Float>();
|
||||
mBufferSize = mCoefficients.length;
|
||||
|
||||
//Fill the buffer with zero valued samples
|
||||
for( int x = 0; x < mCoefficients.length; x++ )
|
||||
{
|
||||
mBuffer.add( 0.0f );
|
||||
}
|
||||
|
||||
generateIndexMap( mCoefficients.length );
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the filtered value by applying the coefficients against
|
||||
* the complex samples in mBuffer
|
||||
*/
|
||||
public void receive( float newSample )
|
||||
{
|
||||
//Add the new sample to the buffer
|
||||
mBuffer.set( mBufferPointer, newSample );
|
||||
|
||||
//Increment & Adjust the buffer pointer for circular wrap around
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBufferSize )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
//Toggle the flag every time, so that we can calculate and dispatch a
|
||||
//sample every other time, when the flag is true (ie decimate by 2)
|
||||
mDispatchFlag = !mDispatchFlag;
|
||||
|
||||
//Calculate a filtered sample when the flag is true
|
||||
if( mDispatchFlag )
|
||||
{
|
||||
//Convolution - multiply filter coefficients by the circular buffer
|
||||
//samples to calculate a new filtered value
|
||||
float accumulator = 0;
|
||||
|
||||
//Start with the center tap value
|
||||
accumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] );
|
||||
|
||||
//For the remaining coefficients, add the symmetric samples, oldest and newest
|
||||
//first, then multiply by the single coefficient
|
||||
for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 )
|
||||
{
|
||||
accumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) );
|
||||
}
|
||||
|
||||
//We're almost finished ... apply gain, cast the floats to shorts and
|
||||
//send it on it's merry way
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( (float)( accumulator * mGain ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an n X (n + 1 / 2) index map enabling quick access to the
|
||||
* circular buffer samples.
|
||||
*
|
||||
* As the buffer shifts right with each subsequent sample, we have to move
|
||||
* the index pointers with it, for efficient access of the samples.
|
||||
*
|
||||
* The first array index value in the index map corresponds to the current
|
||||
* buffer pointer location.
|
||||
*
|
||||
* The second array index value points to the samples that should be
|
||||
* multiplied by the coefficients as follows:
|
||||
*
|
||||
* 0 = center tap sample, to be multiplied by center coefficient
|
||||
*
|
||||
* 0 = sample( 1 )
|
||||
* 1 = sample( size - 1 )
|
||||
*
|
||||
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
|
||||
*
|
||||
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
|
||||
* correspond to the matching ( 3 ) coefficient index.
|
||||
*
|
||||
* @param odd-sized number of filter taps (ie coefficients) and buffer
|
||||
*/
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
//Ensure we have an odd size
|
||||
Validate.isTrue(size % 2 == 1);
|
||||
|
||||
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
|
||||
|
||||
//Set center tap index for coefficients array
|
||||
mCenterCoefficient = ( size - 1 ) / 2;
|
||||
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
|
||||
|
||||
mIndexMap = new int[ size ][ mapWidth ];
|
||||
|
||||
//Setup the first row, buffer pointer index 0, as a starting point
|
||||
for( int x = 0; x < mapWidth - 2; x += 2 )
|
||||
{
|
||||
mIndexMap[ 0 ][ x ] = x;
|
||||
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
|
||||
}
|
||||
|
||||
//Place center tap index in last element
|
||||
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
|
||||
|
||||
//For each subsequent row, increment the previous row's value by 1,
|
||||
//subtracting size as needed, to keep the values between 0 and size - 1
|
||||
for( int x = 1; x < size; x++ )
|
||||
{
|
||||
for( int y = 0; y < mapWidth; y++ )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
|
||||
|
||||
if( mIndexMap[ x ][ y ] >= size )
|
||||
{
|
||||
mIndexMap[ x ][ y ] -= size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener for filtered samples
|
||||
*/
|
||||
public void setListener( RealSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes (if exists) a registered filtered sample listener
|
||||
*/
|
||||
public void clearListener()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
@@ -1,194 +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.dsp.filter;
|
||||
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class FloatHalfBandNoDecimateFilter implements RealSampleListener
|
||||
{
|
||||
private RealSampleListener mListener;
|
||||
private ArrayList<Float> mBuffer;
|
||||
private int mBufferSize = 1; //Temporary initial value
|
||||
private int mBufferPointer = 0;
|
||||
private float mGain;
|
||||
private float[] mCoefficients;
|
||||
private int[][] mIndexMap;
|
||||
private int mCenterCoefficient;
|
||||
private int mCenterCoefficientMapIndex;
|
||||
|
||||
/**
|
||||
* Half-Band filter with decimation by a real valued float.
|
||||
*
|
||||
* Takes advantage of the symmetrical nature of FIR filter coefficients by
|
||||
* adding oldest and newest sample first, then multiplying once by the
|
||||
* corresponding coefficient
|
||||
*
|
||||
* Also, takes advantage of the 0-valued FIR half-band coefficents inherent
|
||||
* in the half-band filter, and does not calculate those coefficients.
|
||||
*
|
||||
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
|
||||
*
|
||||
* @param filter - filter coefficients
|
||||
* @param gain - gain multiplier. Use 1.0 for unity/no gain
|
||||
*/
|
||||
public FloatHalfBandNoDecimateFilter( Filters filter, float gain )
|
||||
{
|
||||
mCoefficients = filter.getCoefficients();
|
||||
mBuffer = new ArrayList<Float>();
|
||||
mBufferSize = mCoefficients.length;
|
||||
|
||||
//Fill the buffer with zero valued samples
|
||||
for( int x = 0; x < mCoefficients.length; x++ )
|
||||
{
|
||||
mBuffer.add( 0.0f );
|
||||
}
|
||||
|
||||
generateIndexMap( mCoefficients.length );
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the filtered value by applying the coefficients against
|
||||
* the complex samples in mBuffer
|
||||
*/
|
||||
public void receive( float newSample )
|
||||
{
|
||||
//Add the new sample to the buffer
|
||||
mBuffer.set( mBufferPointer, newSample );
|
||||
|
||||
//Increment & Adjust the buffer pointer for circular wrap around
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBufferSize )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
//Convolution - multiply filter coefficients by the circular buffer
|
||||
//samples to calculate a new filtered value
|
||||
float accumulator = 0;
|
||||
|
||||
//Start with the center tap value
|
||||
accumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] );
|
||||
|
||||
//For the remaining coefficients, add the symmetric samples, oldest and newest
|
||||
//first, then multiply by the single coefficient
|
||||
for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 )
|
||||
{
|
||||
accumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) );
|
||||
}
|
||||
|
||||
//We're almost finished ... apply gain, cast the floats to floats and
|
||||
//send it on it's merry way
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( (float)( accumulator * mGain ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an n X (n + 1 / 2) index map enabling quick access to the
|
||||
* circular buffer samples.
|
||||
*
|
||||
* As the buffer shifts right with each subsequent sample, we have to move
|
||||
* the index pointers with it, for efficient access of the samples.
|
||||
*
|
||||
* The first array index value in the index map corresponds to the current
|
||||
* buffer pointer location.
|
||||
*
|
||||
* The second array index value points to the samples that should be
|
||||
* multiplied by the coefficients as follows:
|
||||
*
|
||||
* 0 = center tap sample, to be multiplied by center coefficient
|
||||
*
|
||||
* 0 = sample( 1 )
|
||||
* 1 = sample( size - 1 )
|
||||
*
|
||||
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
|
||||
*
|
||||
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
|
||||
* correspond to the matching ( 3 ) coefficient index.
|
||||
*
|
||||
* @param odd-sized number of filter taps (ie coefficients) and buffer
|
||||
*/
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
//Ensure we have an odd size
|
||||
Validate.isTrue(size % 2 == 1);
|
||||
|
||||
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
|
||||
|
||||
//Set center tap index for coefficients array
|
||||
mCenterCoefficient = ( size - 1 ) / 2;
|
||||
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
|
||||
|
||||
mIndexMap = new int[ size ][ mapWidth ];
|
||||
|
||||
//Setup the first row, buffer pointer index 0, as a starting point
|
||||
for( int x = 0; x < mapWidth - 2; x += 2 )
|
||||
{
|
||||
mIndexMap[ 0 ][ x ] = x;
|
||||
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
|
||||
}
|
||||
|
||||
//Place center tap index in last element
|
||||
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
|
||||
|
||||
//For each subsequent row, increment the previous row's value by 1,
|
||||
//subtracting size as needed, to keep the values between 0 and size - 1
|
||||
for( int x = 1; x < size; x++ )
|
||||
{
|
||||
for( int y = 0; y < mapWidth; y++ )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
|
||||
|
||||
if( mIndexMap[ x ][ y ] >= size )
|
||||
{
|
||||
mIndexMap[ x ][ y ] -= size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener for filtered samples
|
||||
*/
|
||||
public void setListener( RealSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes (if exists) a registered filtered sample listener
|
||||
*/
|
||||
public void clearListener()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* 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.dsp.filter;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.window.WindowFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
|
||||
/**
|
||||
* Implements the Goertzel Filter as described in
|
||||
* http://en.wikipedia.org/wiki/Goertzel_algorithm
|
||||
*
|
||||
* <p>
|
||||
* Calculates the magnitude of a specific frequency component within a window of
|
||||
* samples, using a Goertzel Filter (ie an optimized DFT).
|
||||
*/
|
||||
@@ -33,34 +33,27 @@ public class GoertzelFilter
|
||||
private int mSampleRate;
|
||||
private long mTargetFrequency;
|
||||
private int mBlockSize;
|
||||
private double[] mWindowCoefficients;
|
||||
private float[] mWindowCoefficients;
|
||||
|
||||
private double mCoefficient;
|
||||
|
||||
/**
|
||||
* Constructs the Goertzel Filter to use for block processing of
|
||||
* time-domain signal samples.
|
||||
*
|
||||
* @param sampleRate
|
||||
* - sample rate (Hz)
|
||||
* @param targetFrequency
|
||||
* - target frequency (Hz)
|
||||
* @param blockSize
|
||||
* - number of samples per block of processing
|
||||
* @param window
|
||||
* - window to apply against the samples prior to calculating
|
||||
* the magnitude
|
||||
*
|
||||
* @param sampleRate - sample rate (Hz)
|
||||
* @param targetFrequency - target frequency (Hz)
|
||||
* @param blockSize - number of samples per block of processing
|
||||
* @param window - window to apply against the samples prior to calculating
|
||||
* the magnitude
|
||||
*/
|
||||
public GoertzelFilter( int sampleRate,
|
||||
long targetFrequency,
|
||||
int blockSize,
|
||||
WindowType window )
|
||||
public GoertzelFilter(int sampleRate, long targetFrequency, int blockSize, WindowType window)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mTargetFrequency = targetFrequency;
|
||||
mBlockSize = blockSize;
|
||||
mWindowCoefficients = Window.getWindow( window, blockSize );
|
||||
init();
|
||||
mSampleRate = sampleRate;
|
||||
mTargetFrequency = targetFrequency;
|
||||
mBlockSize = blockSize;
|
||||
mWindowCoefficients = WindowFactory.getWindow(window, blockSize);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,77 +61,74 @@ public class GoertzelFilter
|
||||
*/
|
||||
private void init()
|
||||
{
|
||||
double normalizedFrequency = (double) mTargetFrequency / mSampleRate;
|
||||
mCoefficient = 2.0D * FastMath.cos( 2 * FastMath.PI * normalizedFrequency );
|
||||
double normalizedFrequency = (double)mTargetFrequency / mSampleRate;
|
||||
mCoefficient = 2.0D * FastMath.cos(2 * FastMath.PI * normalizedFrequency);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the power (dB) of the target frequency within the block of
|
||||
* Returns the power (dB) of the target frequency within the block of
|
||||
* samples as normalized against the bin(0) magnitude value
|
||||
*
|
||||
* @param samples
|
||||
* - array of time-domain samples -- array size must be the same
|
||||
* size as the windowSize parameter passed upon construction
|
||||
*
|
||||
*
|
||||
* @param samples - array of time-domain samples -- array size must be the same
|
||||
* size as the windowSize parameter passed upon construction
|
||||
* @return - power measurement in dB
|
||||
|
||||
* @throws IllegalArgumentException if the sample array size is not equal
|
||||
* to the defined block size
|
||||
* to the defined block size
|
||||
*/
|
||||
public int getPower( float[] samples ) throws IllegalArgumentException
|
||||
public int getPower(float[] samples) throws IllegalArgumentException
|
||||
{
|
||||
// Verify size of samples array against block size
|
||||
if ( samples.length != mBlockSize )
|
||||
{
|
||||
throw new IllegalArgumentException(
|
||||
"Sample array size does not equal Block Size" );
|
||||
}
|
||||
|
||||
// Apply the window against the samples
|
||||
samples = Window.apply( mWindowCoefficients, samples );
|
||||
|
||||
// Process the samples
|
||||
double s = 0.0D;
|
||||
double s_prev = 0.0D;
|
||||
double s_prev2 = 0.0D;
|
||||
// Verify size of samples array against block size
|
||||
if(samples.length != mBlockSize)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample array size does not equal Block Size");
|
||||
}
|
||||
|
||||
for (float sample : samples) {
|
||||
// Apply the window against the samples
|
||||
samples = WindowFactory.apply(mWindowCoefficients, samples);
|
||||
|
||||
// Process the samples
|
||||
double s = 0.0D;
|
||||
double s_prev = 0.0D;
|
||||
double s_prev2 = 0.0D;
|
||||
|
||||
for(float sample : samples)
|
||||
{
|
||||
s = sample + (mCoefficient * s_prev) - s_prev2;
|
||||
s_prev2 = s_prev;
|
||||
s_prev = s;
|
||||
}
|
||||
|
||||
//power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2 ;
|
||||
|
||||
double magnitude = ( s_prev2 * s_prev2 ) + ( s_prev * s_prev) - (mCoefficient * s_prev * s_prev2);
|
||||
int binZero = getBinZeroPower( samples );
|
||||
|
||||
int power = (int) (20 * FastMath.log10( magnitude / binZero ) );
|
||||
|
||||
return power;
|
||||
|
||||
//power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2 ;
|
||||
|
||||
double magnitude = (s_prev2 * s_prev2) + (s_prev * s_prev) - (mCoefficient * s_prev * s_prev2);
|
||||
int binZero = getBinZeroPower(samples);
|
||||
|
||||
int power = (int)(20 * FastMath.log10(magnitude / binZero));
|
||||
|
||||
return power;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sums all of the sample values to derive the bin 0 power
|
||||
*/
|
||||
private int getBinZeroPower( float[] samples )
|
||||
private int getBinZeroPower(float[] samples)
|
||||
{
|
||||
int retVal = 0;
|
||||
|
||||
for (float sample : samples) {
|
||||
for(float sample : samples)
|
||||
{
|
||||
retVal += sample;
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the Sample Rate
|
||||
*/
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +136,7 @@ public class GoertzelFilter
|
||||
*/
|
||||
public long getTargetFrequency()
|
||||
{
|
||||
return mTargetFrequency;
|
||||
return mTargetFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +144,7 @@ public class GoertzelFilter
|
||||
*/
|
||||
public int getBlockSize()
|
||||
{
|
||||
return mBlockSize;
|
||||
return mBlockSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,505 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter;
|
||||
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Window factory and corresponding utility methods.
|
||||
*/
|
||||
public class Window
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Window.class);
|
||||
|
||||
private static final double TWO_PI = FastMath.PI * 2.0d;
|
||||
private static final double FOUR_PI = FastMath.PI * 4.0d;
|
||||
private static final double SIX_PI = FastMath.PI * 6.0d;
|
||||
private static final double EIGHT_PI = FastMath.PI * 8.0d;
|
||||
|
||||
/**
|
||||
* Creates a window of the specified type and length.
|
||||
*
|
||||
* @param type of window
|
||||
* @param length of window
|
||||
* @return window of the specified type and length
|
||||
*/
|
||||
public static double[] getWindow(WindowType type, int length)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case BLACKMAN:
|
||||
return getBlackman(length);
|
||||
case BLACKMAN_HARRIS_4:
|
||||
return getBlackmanHarris4(length);
|
||||
case BLACKMAN_HARRIS_7:
|
||||
return getBlackmanHarris7(length);
|
||||
case BLACKMAN_NUTALL:
|
||||
return getBlackmanNutall(length);
|
||||
case COSINE:
|
||||
return getCosine(length);
|
||||
case FLAT_TOP:
|
||||
return getFlatTop(length);
|
||||
case HAMMING:
|
||||
return getHamming(length);
|
||||
case HANN:
|
||||
return getHann(length);
|
||||
case KAISER:
|
||||
throw new IllegalArgumentException("Kaiser Window cannot be created via this method. Use the getKaiser() method instead.");
|
||||
case NUTALL:
|
||||
return getNutall(length);
|
||||
case NONE:
|
||||
default:
|
||||
return getRectangular(length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an all-pass or rectangular window.
|
||||
*
|
||||
* @param length of the window
|
||||
* @return rectangular window
|
||||
*/
|
||||
public static double[] getRectangular(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
Arrays.fill(coefficients, 1.0d);
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cosine window of the specified length.
|
||||
*
|
||||
* @param length of the window
|
||||
* @return cosine window
|
||||
*/
|
||||
public static double[] getCosine(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
if(length % 2 == 0) //Even length
|
||||
{
|
||||
int half = (int)((length - 1) / 2);
|
||||
|
||||
for(int x = -half; x < length / 2 + 1; x++)
|
||||
{
|
||||
coefficients[x + half] = FastMath.cos(
|
||||
((double)x * FastMath.PI) / ((double)length + 1.0d));
|
||||
}
|
||||
}
|
||||
else //Odd length
|
||||
{
|
||||
int half = (int)length / 2;
|
||||
|
||||
for(int x = -half; x < half + 1; x++)
|
||||
{
|
||||
coefficients[x + half] = FastMath.cos(
|
||||
((double)x * FastMath.PI) / ((double)length + 1.0d));
|
||||
}
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 3-term Blackman window.
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Blackman window
|
||||
*/
|
||||
public static double[] getBlackman(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double denominator = length - 1;
|
||||
|
||||
double a0 = 0.426590713672;
|
||||
double a1 = 0.496560619089;
|
||||
double a2 = 0.0768486672399;
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = a0 -
|
||||
(a1 * FastMath.cos((TWO_PI * (double)x) / denominator)) +
|
||||
(a2 * FastMath.cos((FOUR_PI * (double)x) / denominator));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 4-term Nutall window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Nutall window
|
||||
*/
|
||||
public static double[] getNutall(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double denominator = length - 1;
|
||||
double a0 = 0.355768;
|
||||
double a1 = 0.487396;
|
||||
double a2 = 0.144232;
|
||||
double a3 = 0.012604;
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = a0 -
|
||||
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
|
||||
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
|
||||
(a3 * FastMath.cos(SIX_PI * (double)x / denominator));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 4-term Blackman-Nutall window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Blackman-Nutall window
|
||||
*/
|
||||
public static double[] getBlackmanNutall(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double denominator = length - 1;
|
||||
double a0 = 0.3635819;
|
||||
double a1 = 0.4891775;
|
||||
double a2 = 0.1365995;
|
||||
double a3 = 0.0106411;
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = a0 -
|
||||
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
|
||||
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
|
||||
(a3 * FastMath.cos(SIX_PI * (double)x / denominator));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 4-term Blackman-Harris window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Blackman-Harris window
|
||||
*/
|
||||
public static double[] getBlackmanHarris4(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double denominator = length - 1;
|
||||
double a0 = 0.35875;
|
||||
double a1 = 0.48829;
|
||||
double a2 = 0.14128;
|
||||
double a3 = 0.01168;
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = a0 -
|
||||
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
|
||||
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
|
||||
(a3 * FastMath.cos(SIX_PI * (double)x / denominator));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 7-term Blackman-Harris window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Blackman-Harris window
|
||||
*/
|
||||
public static double[] getBlackmanHarris7(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double denominator = length - 1;
|
||||
|
||||
double a0 = 0.27105140069342;
|
||||
double a1 = 0.43329793923448;
|
||||
double a2 = 0.21812299954311;
|
||||
double a3 = 0.06592544638803;
|
||||
double a4 = 0.01081174209837;
|
||||
double a5 = 0.00077658482522;
|
||||
double a6 = 0.00001388721735;
|
||||
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
double w = TWO_PI * (double)x / denominator;
|
||||
|
||||
coefficients[x] = a0 -
|
||||
(a1 * FastMath.cos(w)) +
|
||||
(a2 * FastMath.cos(2.0 * w)) -
|
||||
(a3 * FastMath.cos(3.0 * w)) +
|
||||
(a4 * FastMath.cos(4.0 * w)) -
|
||||
(a5 * FastMath.cos(5.0 * w)) +
|
||||
(a6 * FastMath.cos(6.0 * w));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Hamming window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Hamming window
|
||||
*/
|
||||
public static double[] getHamming(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = 0.54 - (0.46 * FastMath.cos((TWO_PI * x) / (length - 1)));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Hann window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Hann window
|
||||
*/
|
||||
public static double[] getHann(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = 0.5 - (0.5 * FastMath.cos(TWO_PI * (double)x / (length - 1)));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 4-term Blackman-Harris window
|
||||
*
|
||||
* @param length of the window
|
||||
* @return Blackman-Harris window
|
||||
*/
|
||||
public static double[] getFlatTop(int length)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double denominator = length - 1;
|
||||
|
||||
double a0 = 0.215578948;
|
||||
double a1 = 0.41663158;
|
||||
double a2 = 0.277263158;
|
||||
double a3 = 0.083578947;
|
||||
double a4 = 0.006947368;
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
coefficients[x] = a0 -
|
||||
(a1 * FastMath.cos(TWO_PI * (double)x / denominator)) +
|
||||
(a2 * FastMath.cos(FOUR_PI * (double)x / denominator)) -
|
||||
(a3 * FastMath.cos(SIX_PI * (double)x / denominator)) +
|
||||
(a4 * FastMath.cos(EIGHT_PI * (double)x / denominator));
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the beta (roll-off factor) for a Kaiser window based on the specified attenuation.
|
||||
*
|
||||
* @param attenuation in decibels
|
||||
* @return beta value in the range of 0.0 to 1.0
|
||||
*/
|
||||
public static double getKaiserBeta(double attenuation)
|
||||
{
|
||||
if(attenuation > 50.0)
|
||||
{
|
||||
return 0.1102 * (attenuation - 8.7);
|
||||
}
|
||||
else if(attenuation >= 21.0)
|
||||
{
|
||||
return (0.5842 * FastMath.pow(attenuation - 21.0, 0.4)) + (0.07886 * (attenuation - 21.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Kaiser window.
|
||||
*
|
||||
* @param length of the window
|
||||
* @param attenuation desired
|
||||
* @return window
|
||||
*/
|
||||
public static double[] getKaiser(int length, double attenuation)
|
||||
{
|
||||
double[] coefficients = new double[length];
|
||||
|
||||
double beta = getKaiserBeta(attenuation);
|
||||
|
||||
double betaBesselZerothOrder = getBesselZerothOrder(beta);
|
||||
|
||||
double temp;
|
||||
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
temp = beta * FastMath.sqrt(1.0 - FastMath.pow(2.0 * x / (length - 1) - 1.0, 2));
|
||||
coefficients[x] = getBesselZerothOrder(temp) / betaBesselZerothOrder;
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Zeroth order modified Bessel function.
|
||||
*
|
||||
* @param x Value.
|
||||
* @return Return value.
|
||||
*/
|
||||
public final static double getBesselZerothOrder(final double x)
|
||||
{
|
||||
double f = 1;
|
||||
final double x2 = x * x * 0.25;
|
||||
double xc = x2;
|
||||
double v = 1 + x2;
|
||||
for (int i = 2; i < 100; i++)
|
||||
{
|
||||
f *= i;
|
||||
xc *= x2;
|
||||
final double a = xc / (f * f);
|
||||
v += a;
|
||||
if (a < 1e-20) break;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the window against an array of float-type samples
|
||||
*/
|
||||
public static float[] apply(double[] coefficients, float[] samples)
|
||||
{
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
samples[x] = (float)(samples[x] * coefficients[x]);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the window type against the float array of samples
|
||||
*
|
||||
* @param type of window to use
|
||||
* @param samples to be windowed
|
||||
* @return windowed samples
|
||||
*/
|
||||
public static float[] apply(WindowType type, float[] samples)
|
||||
{
|
||||
double[] coefficients = getWindow(type, samples.length);
|
||||
|
||||
return apply(coefficients, samples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the window against an array of double-type samples
|
||||
*/
|
||||
public static double[] apply(double[] coefficients, double[] samples)
|
||||
{
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
samples[x] = samples[x] * coefficients[x];
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the window type against the double array of samples
|
||||
*
|
||||
* @param type of window to use
|
||||
* @param samples to be windowed
|
||||
* @return windowed samples
|
||||
*/
|
||||
public static double[] apply(WindowType type, double[] samples)
|
||||
{
|
||||
double[] coefficients = getWindow(type, samples.length);
|
||||
|
||||
return apply(coefficients, samples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Window types
|
||||
*/
|
||||
public enum WindowType
|
||||
{
|
||||
BLACKMAN("Blackman"),
|
||||
BLACKMAN_HARRIS_4("Blackman-Harris 4"),
|
||||
BLACKMAN_HARRIS_7("Blackman-Harris 7"),
|
||||
BLACKMAN_NUTALL("Blackman-Nutall"),
|
||||
COSINE("Cosine"),
|
||||
FLAT_TOP("Flat Top"),
|
||||
HAMMING("Hamming"),
|
||||
HANN("Hann"),
|
||||
KAISER("Kaiser"),
|
||||
NUTALL("Nutall"),
|
||||
NONE("None");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
WindowType(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public static final EnumSet<WindowType> NO_PARAMETER_WINDOWS = EnumSet.of(WindowType.BLACKMAN, WindowType.BLACKMAN_HARRIS_4,
|
||||
WindowType.BLACKMAN_HARRIS_7, WindowType.BLACKMAN_NUTALL, WindowType.COSINE, WindowType.FLAT_TOP,
|
||||
WindowType.HAMMING, WindowType.HANN, WindowType.NUTALL);
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
double[] kaiser = getKaiser(16, 80.0);
|
||||
|
||||
mLog.debug("Window:" + Arrays.toString(kaiser));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
@@ -14,15 +14,13 @@
|
||||
*
|
||||
* 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.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -31,10 +29,9 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ReusableComplexBuffer>, ISourceEventListener
|
||||
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<InterleavedComplexSamples>, ISourceEventListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class);
|
||||
private ReusableChannelResultsBufferQueue mBufferQueue = new ReusableChannelResultsBufferQueue("AbstractComplexPolyphaseChannelizer");
|
||||
private Broadcaster<SourceEvent> mSourceChangeBroadcaster = new Broadcaster();
|
||||
private List<PolyphaseChannelSource> mChannels = new CopyOnWriteArrayList<>();
|
||||
private double mSampleRate;
|
||||
@@ -103,17 +100,14 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Re
|
||||
/**
|
||||
* Dispatches the processed channel samples to any registered polyphase channel outputs.
|
||||
*
|
||||
* @param channelResultsBuffer containing an array of an array of I/Q samples per channel
|
||||
* @param channelResultsList a list of arrays of I/Q samples per channel
|
||||
*/
|
||||
protected void dispatch(ReusableChannelResultsBuffer channelResultsBuffer)
|
||||
protected void dispatch(List<float[]> channelResultsList)
|
||||
{
|
||||
for(PolyphaseChannelSource channel : mChannels)
|
||||
{
|
||||
channelResultsBuffer.incrementUserCount();
|
||||
channel.receiveChannelResults(channelResultsBuffer);
|
||||
channel.receiveChannelResults(channelResultsList);
|
||||
}
|
||||
|
||||
channelResultsBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,16 +146,6 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<Re
|
||||
return mChannels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or reuses a channel results buffer for subclass implementations to use for temporary
|
||||
* storage of channel results through distribution and consumption of channel results by
|
||||
* channel output processors.
|
||||
*/
|
||||
protected ReusableChannelResultsBuffer getChannelResultsBuffer()
|
||||
{
|
||||
return mBufferQueue.getBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
@@ -14,24 +14,22 @@
|
||||
*
|
||||
* 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.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -67,8 +65,13 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
||||
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
|
||||
|
||||
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
|
||||
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));
|
||||
/**
|
||||
* Determines how many processed channel results to dispatch for threaded IFFT processing per batch
|
||||
*/
|
||||
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
||||
|
||||
//Sized at 152 buffers a second where max = 5 seconds
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152);
|
||||
private FloatFFT_1D mFFT;
|
||||
private float[] mInlineSamples;
|
||||
private float[] mInlineFilter;
|
||||
@@ -78,6 +81,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
private int mSampleBufferPointer;
|
||||
private int mSamplesPerBlock;
|
||||
private int mTapsPerChannel;
|
||||
private List<float[]> mProcessedChannelResultsList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a NMDPFB channelizer instance.
|
||||
@@ -128,7 +132,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
mIFFTProcessor.start();
|
||||
mIFFTProcessorDispatcher.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +140,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mIFFTProcessor.stop();
|
||||
mIFFTProcessorDispatcher.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,12 +191,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
* Receives the complex sample buffer and processes the results through the channelizer.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
public void receive(InterleavedComplexSamples complexSamples)
|
||||
{
|
||||
ReusableChannelResultsBuffer channelResultsBuffer = getChannelResultsBuffer();
|
||||
channelResultsBuffer.setTimestamp(reusableComplexBuffer.getTimestamp());
|
||||
|
||||
float[] samples = reusableComplexBuffer.getSamples();
|
||||
//TODO: how do we pass the timestamp onto the sample assemblers?
|
||||
float[] samples = complexSamples.samples();
|
||||
|
||||
int samplesPointer = 0;
|
||||
int samplesToCopy;
|
||||
@@ -218,20 +220,19 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
if(mSampleBufferPointer >= mSamplesPerBlock)
|
||||
{
|
||||
//Filter buffered samples and produce a single sample across each of the polyphase channels
|
||||
process(channelResultsBuffer);
|
||||
mProcessedChannelResultsList.add(process());
|
||||
|
||||
if(mProcessedChannelResultsList.size() >= PROCESSED_CHANNEL_RESULTS_THRESHOLD)
|
||||
{
|
||||
mIFFTProcessorDispatcher.receive(new ArrayList<>(mProcessedChannelResultsList));
|
||||
mProcessedChannelResultsList.clear();
|
||||
}
|
||||
|
||||
//Right-shift the samples in the buffer over to make room for a new block of samples
|
||||
//Note: since JDK 8, hotspot JIT compiler uses native processor intrinsics for efficiency
|
||||
System.arraycopy(mInlineSamples, 0, mInlineSamples, mSamplesPerBlock, (mInlineSamples.length - mSamplesPerBlock));
|
||||
mSampleBufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//Enqueue the channel results buffer for IFFT processing and distribution on a different thread
|
||||
mIFFTProcessor.receive(channelResultsBuffer);
|
||||
|
||||
//Decrement the user count to let the originator know we're done with their buffer
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,7 +335,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
* Processes the sample buffer for each new block of sample data that is loaded and distributes the results to any
|
||||
* registered channel listeners.
|
||||
*/
|
||||
private void process(ReusableChannelResultsBuffer channelResultsBuffer)
|
||||
private float[] process()
|
||||
{
|
||||
int bufferLength = getSubChannelCount() * mTapsPerChannel;
|
||||
float[] inlineInterimOutput = new float[bufferLength];
|
||||
@@ -360,7 +361,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
}
|
||||
}
|
||||
|
||||
float[] processed = channelResultsBuffer.getEmptyBuffer(getSubChannelCount());
|
||||
float[] processed = new float[getSubChannelCount()];
|
||||
|
||||
if(mTopBlockIndicator)
|
||||
{
|
||||
@@ -377,9 +378,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
}
|
||||
}
|
||||
|
||||
channelResultsBuffer.addChannelResults(processed);
|
||||
|
||||
mTopBlockIndicator = !mTopBlockIndicator;
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,10 +390,10 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
private void init(float[] coefficients)
|
||||
{
|
||||
mFFT = new FloatFFT_1D(getChannelCount());
|
||||
int channelCount = getChannelCount();
|
||||
mFFT = new FloatFFT_1D(channelCount);
|
||||
int bufferLength = getSubChannelCount() * mTapsPerChannel;
|
||||
mSamplesPerBlock = getChannelCount(); //Same as subChannelCount / 2
|
||||
mSamplesPerBlock = channelCount; //Same as subChannelCount / 2
|
||||
mTopBlockMap = getTopBlockMap(channelCount);
|
||||
mMiddleBlockMap = getMiddleBlockMap(channelCount);
|
||||
mInlineFilter = getAlignedFilter(coefficients, channelCount, mTapsPerChannel);
|
||||
@@ -404,46 +405,28 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
* as required to align the phase of each polyphase channel, and then dispatch the results to any registered
|
||||
* sample consumer channels.
|
||||
*/
|
||||
public class IFFTProcessor extends ContinuousBufferProcessor<ReusableChannelResultsBuffer>
|
||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||
{
|
||||
public IFFTProcessor(int maximumSize, int resetThreshold)
|
||||
public IFFTProcessorDispatcher(int maximumSize)
|
||||
{
|
||||
super(maximumSize, resetThreshold);
|
||||
super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList());
|
||||
|
||||
//We create a listener interface to receive the buffers from the scheduled thread pool
|
||||
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
|
||||
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
|
||||
//channel results array contained in each results buffer and then dispatch the buffer
|
||||
//so that it can be distributed to each channel listener.
|
||||
setListener(buffers -> {
|
||||
for(ReusableChannelResultsBuffer buffer: buffers)
|
||||
setListener(list -> {
|
||||
List<float[]> processedChannelResults = new ArrayList<>();
|
||||
|
||||
for(float[] channelResults: list)
|
||||
{
|
||||
for(float[] channelResults: buffer.getChannelResults())
|
||||
{
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(channelResults, true);
|
||||
}
|
||||
|
||||
dispatch(buffer);
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(channelResults, true);
|
||||
processedChannelResults.add(channelResults);
|
||||
}
|
||||
|
||||
dispatch(processedChannelResults);
|
||||
});
|
||||
|
||||
setOverflowListener(overflow -> mLog.debug("IFFTProcessor overflow changed - overflow:" + overflow));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any buffers from the dispatch/processing queue. Overrides the parent method so that we can set
|
||||
* the user count to 0 to allow the buffer to be reclaimed.
|
||||
*/
|
||||
protected Collection<ReusableChannelResultsBuffer> clearQueue()
|
||||
{
|
||||
Collection<ReusableChannelResultsBuffer> buffersToDispose = super.clearQueue();
|
||||
|
||||
for(ReusableChannelResultsBuffer buffer: buffersToDispose)
|
||||
{
|
||||
buffer.clearUserCount();
|
||||
}
|
||||
|
||||
return buffersToDispose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ContinuousBufferProcessor<E> implements Listener<E>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ContinuousBufferProcessor.class);
|
||||
|
||||
protected OverflowableTransferQueue<E> mQueue;
|
||||
private Listener<List<E>> mListener;
|
||||
private ScheduledFuture<?> mScheduledFuture;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private long mProcessingPeriod = 5; //milliseconds
|
||||
|
||||
/**
|
||||
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
|
||||
* for periodically distributing internally queued elements to the registered listener. This processor provides
|
||||
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||
* processing workload.
|
||||
*
|
||||
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||
*
|
||||
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
|
||||
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
|
||||
*/
|
||||
public ContinuousBufferProcessor(int maximumSize, int resetThreshold)
|
||||
{
|
||||
this(new OverflowableTransferQueue<>(maximumSize, resetThreshold));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive the queued buffers each time this processor runs.
|
||||
*/
|
||||
protected Listener<List<E>> getListener()
|
||||
{
|
||||
return mListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the processing periodicity or run interval for how often the buffer queue is processed.
|
||||
*
|
||||
* @param milliseconds (default = 5 milliseconds)
|
||||
*/
|
||||
public void setProcessingPeriod(long milliseconds)
|
||||
{
|
||||
mProcessingPeriod = milliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
|
||||
* for periodically distributing internally queued elements to the registered listener. This processor provides
|
||||
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||
* processing workload.
|
||||
*
|
||||
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||
*
|
||||
* @param queue implmentation of an overflowable transfer queue
|
||||
*/
|
||||
public ContinuousBufferProcessor(OverflowableTransferQueue<E> queue)
|
||||
{
|
||||
mQueue = queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive notifications of buffer overflow and/or reset. Note: this method can also be
|
||||
* invoked with a null argument to clear the previously registered overflow listener.
|
||||
*
|
||||
* @param listener to receive overflow/reset notifications.
|
||||
*/
|
||||
public void setOverflowListener(IOverflowListener listener)
|
||||
{
|
||||
mQueue.setOverflowListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the listener to receive buffers from this processor.
|
||||
* @param listener to receive buffers
|
||||
*/
|
||||
public void setListener(Listener<List<E>> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for adding buffers to this processor. Note: incoming buffers will be ignored if this
|
||||
* processor is in a stopped state. You must invoke start() to allow incoming buffers and initiate buffer
|
||||
* processing.
|
||||
*
|
||||
* @param e to enqueue for distribution to a registered listener
|
||||
*/
|
||||
@Override
|
||||
public void receive(E e)
|
||||
{
|
||||
mQueue.offer(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts this buffer processor and allows queuing of incoming buffers.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mProcessingPeriod, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this buffer processor, stops queuing of incoming buffers and clears the internal buffer queue.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
|
||||
clearQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this buffer processor,stops queuing of incoming buffers and flushes the internal buffer queue
|
||||
* contents to the listener.
|
||||
*/
|
||||
public void flushAndStop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
|
||||
process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Distributes queued buffers to the listener
|
||||
*/
|
||||
protected void process()
|
||||
{
|
||||
List<E> buffers = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
mQueue.drainTo(buffers);
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(buffers);
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while dispatching buffers to listener", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any buffers from the queue
|
||||
*/
|
||||
protected Collection<E> clearQueue()
|
||||
{
|
||||
Collection<E> clearedElements = new ArrayList<>();
|
||||
mQueue.drainTo(clearedElements);
|
||||
return clearedElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this processor is currently running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor to service the buffer queue and distribute the buffers to the registered listener
|
||||
*/
|
||||
class Processor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
process();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ContinuousReusableBufferProcessor<T extends AbstractReusableBuffer> extends ContinuousBufferProcessor<T>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ContinuousReusableBufferProcessor.class);
|
||||
|
||||
/**
|
||||
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing
|
||||
* task for periodically distributing internally queued elements to the registered listener. This processor
|
||||
* provides a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||
* processing workload.
|
||||
*
|
||||
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||
*
|
||||
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
|
||||
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
|
||||
*/
|
||||
public ContinuousReusableBufferProcessor(int maximumSize, int resetThreshold)
|
||||
{
|
||||
super(new OverflowableReusableBufferTransferQueue<T>(maximumSize, resetThreshold));
|
||||
}
|
||||
|
||||
/**
|
||||
* Distributes queued buffers to the listener
|
||||
*/
|
||||
@Override
|
||||
protected void process()
|
||||
{
|
||||
List<T> buffers = new ArrayList<>();
|
||||
|
||||
mQueue.drainTo(buffers);
|
||||
|
||||
try
|
||||
{
|
||||
if(getListener() != null)
|
||||
{
|
||||
getListener().receive(buffers);
|
||||
}
|
||||
|
||||
buffers.clear();
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while dispatching buffers to listener. Performing buffer user count cleanup", throwable);
|
||||
|
||||
if(!buffers.isEmpty())
|
||||
{
|
||||
for(T buffer: buffers)
|
||||
{
|
||||
try
|
||||
{
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
catch(IllegalStateException ise)
|
||||
{
|
||||
mLog.error("Error while performing user count cleanup on reusable buffers.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferProvider;
|
||||
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcessor;
|
||||
@@ -28,21 +28,21 @@ import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcesso
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableComplexBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
@@ -76,32 +76,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
|
||||
|
||||
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||
private IReusableComplexBufferProvider mReusableBufferProvider;
|
||||
private INativeBufferProvider mNativeBufferProvider;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private ChannelCalculator mChannelCalculator;
|
||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||
private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener();
|
||||
private BufferSourceEventMonitor mBufferSourceEventMonitor = new BufferSourceEventMonitor();
|
||||
private ContinuousBufferProcessor<ReusableComplexBuffer> mBufferProcessor;
|
||||
private NativeBufferReceiver mNativeBufferReceiver = new NativeBufferReceiver();
|
||||
private Dispatcher mBufferDispatcher;
|
||||
private Map<Integer,float[]> mOutputProcessorFilters = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a polyphase channel manager instance.
|
||||
*
|
||||
* @param reusableComplexBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer
|
||||
* @param nativeBufferProvider (ie tuner) that supports register/deregister for reusable baseband sample buffer
|
||||
* streams
|
||||
* @param frequency of the baseband complex buffer sample stream (ie center frequency)
|
||||
* @param sampleRate of the baseband complex buffer sample stream
|
||||
*/
|
||||
public PolyphaseChannelManager(IReusableComplexBufferProvider reusableComplexBufferProvider,
|
||||
long frequency, double sampleRate)
|
||||
public PolyphaseChannelManager(INativeBufferProvider nativeBufferProvider, long frequency, double sampleRate)
|
||||
{
|
||||
if(reusableComplexBufferProvider == null)
|
||||
if(nativeBufferProvider == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
|
||||
}
|
||||
|
||||
mReusableBufferProvider = reusableComplexBufferProvider;
|
||||
mNativeBufferProvider = nativeBufferProvider;
|
||||
|
||||
int channelCount = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
|
||||
|
||||
@@ -112,9 +111,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
|
||||
mBufferProcessor = new ContinuousBufferProcessor(200, 50);
|
||||
mBufferProcessor.setListener(mBufferSourceEventMonitor);
|
||||
mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor",
|
||||
new NativeBufferPoisonPill());
|
||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +160,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
* @param tunerChannel specifying center frequency and bandwidth.
|
||||
* @return source or null.
|
||||
*/
|
||||
public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
|
||||
public TunerChannelSource getChannel(TunerChannel tunerChannel)
|
||||
{
|
||||
PolyphaseChannelSource channelSource = null;
|
||||
|
||||
@@ -176,7 +175,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
try
|
||||
{
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
|
||||
mChannelCalculator.getChannelSampleRate(), centerFrequency);
|
||||
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
@@ -229,7 +228,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
*/
|
||||
private void startChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
synchronized(mBufferProcessor)
|
||||
synchronized(mBufferDispatcher)
|
||||
{
|
||||
//Note: the polyphase channel source has already been added to the mChannelSources in getChannel() method
|
||||
|
||||
@@ -241,9 +240,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
//If this is the first channel, register to start the sample buffers flowing
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
||||
{
|
||||
mReusableBufferProvider.addBufferListener(mBufferProcessor);
|
||||
mNativeBufferProvider.addBufferListener(mBufferDispatcher);
|
||||
mPolyphaseChannelizer.start();
|
||||
mBufferProcessor.start();
|
||||
mBufferDispatcher.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +255,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
*/
|
||||
private void stopChannelSource(PolyphaseChannelSource channelSource)
|
||||
{
|
||||
synchronized(mBufferProcessor)
|
||||
synchronized(mBufferDispatcher)
|
||||
{
|
||||
mChannelSources.remove(channelSource);
|
||||
mPolyphaseChannelizer.removeChannel(channelSource);
|
||||
@@ -265,8 +264,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
//If this is the last/only channel, deregister to stop the sample buffers
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
||||
{
|
||||
mReusableBufferProvider.removeBufferListener(mBufferProcessor);
|
||||
mBufferProcessor.stop();
|
||||
mNativeBufferProvider.removeBufferListener(mBufferDispatcher);
|
||||
mBufferDispatcher.stop();
|
||||
mPolyphaseChannelizer.stop();
|
||||
}
|
||||
}
|
||||
@@ -298,7 +297,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
||||
|
||||
//Defer channelizer configuration changes to be handled on the buffer processor thread
|
||||
mBufferSourceEventMonitor.receive(sourceEvent);
|
||||
mNativeBufferReceiver.receive(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
//Update channel calculator immediately so that channels can be allocated
|
||||
@@ -565,7 +564,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
* 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.
|
||||
*/
|
||||
public class BufferSourceEventMonitor implements Listener<List<ReusableComplexBuffer>>
|
||||
public class NativeBufferReceiver implements Listener<INativeBuffer>
|
||||
{
|
||||
private Queue<SourceEvent> mQueuedSourceEvents = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@@ -579,7 +578,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(List<ReusableComplexBuffer> reusableComplexBuffers)
|
||||
public void receive(INativeBuffer nativeBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -599,26 +598,19 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
queuedSourceEvent = mQueuedSourceEvents.poll();
|
||||
}
|
||||
|
||||
for(ReusableComplexBuffer reusableComplexBuffer: reusableComplexBuffers)
|
||||
if(mPolyphaseChannelizer != null)
|
||||
{
|
||||
if(mPolyphaseChannelizer != null)
|
||||
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
|
||||
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
//User count management is handled by the channelizer
|
||||
mPolyphaseChannelizer.receive(reusableComplexBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
mPolyphaseChannelizer.receive(iterator.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error", throwable);
|
||||
for(ReusableComplexBuffer buffer: reusableComplexBuffers)
|
||||
{
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,36 +20,23 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
public class PolyphaseChannelSource extends TunerChannelSource
|
||||
public class PolyphaseChannelSource extends TunerChannelSource implements Listener<ComplexSamples>
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
|
||||
private static final int PROCESSED_BUFFER_SAMPLE_SIZE = 2048;
|
||||
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
|
||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
|
||||
private long mReplacementFrequency;
|
||||
private ComplexFIRFilter2 mLowPassFilter;
|
||||
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
|
||||
|
||||
private double mChannelSampleRate;
|
||||
private long mIndexCenterFrequency;
|
||||
private long mChannelFrequencyCorrection;
|
||||
private Listener<ComplexSamples> mComplexSamplesListener;
|
||||
|
||||
/**
|
||||
* Polyphase channelizer tuner channel source implementation. Adapts the channel array output samples from the
|
||||
@@ -65,37 +52,57 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
*/
|
||||
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
|
||||
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
|
||||
long centerFrequency, ChannelSpecification channelSpecification)
|
||||
throws FilterDesignException
|
||||
long centerFrequency) throws FilterDesignException
|
||||
{
|
||||
super(producerSourceEventListener, tunerChannel);
|
||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mChannelSampleRate = channelSampleRate;
|
||||
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(PROCESSED_BUFFER_SAMPLE_SIZE, mChannelSampleRate);
|
||||
|
||||
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
|
||||
channelSpecification.getStopFrequency());
|
||||
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
|
||||
|
||||
setFrequency(centerFrequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
super.stop();
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive complex sample buffers from this channel source
|
||||
*/
|
||||
@Override
|
||||
public void setListener(final Listener<ReusableComplexBuffer> listener)
|
||||
public void setListener(final Listener<ComplexSamples> listener)
|
||||
{
|
||||
mReusableComplexBufferAssembler.setListener(listener);
|
||||
}
|
||||
mComplexSamplesListener = listener;
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving complex sample buffers from this channel source
|
||||
*/
|
||||
@Override
|
||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||
public void receive(ComplexSamples complexSamples)
|
||||
{
|
||||
mReusableComplexBufferAssembler.setListener(null);
|
||||
getHeartbeatManager().broadcast();
|
||||
|
||||
if(mComplexSamplesListener != null)
|
||||
{
|
||||
mComplexSamplesListener.receive(complexSamples);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +126,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
if(mPolyphaseChannelOutputProcessor == null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
}
|
||||
//Otherwise, we have to swap out the processor on the sample processing thread
|
||||
else
|
||||
@@ -138,17 +146,14 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
if(mReplacementPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
|
||||
existingProcessor.stop();
|
||||
existingProcessor.setListener(null);
|
||||
|
||||
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
|
||||
mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor;
|
||||
mReplacementPolyphaseChannelOutputProcessor = null;
|
||||
|
||||
//Remove the buffer overflow listener and clear any previous buffer overflow state
|
||||
existingProcessor.setSourceOverflowListener(null);
|
||||
broadcastOverflowState(false);
|
||||
|
||||
//Register to receive buffer overflow notifications so we can push them up through the source chain
|
||||
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
|
||||
//Finally, setup the frequency offset for the output processor.
|
||||
mIndexCenterFrequency = mReplacementFrequency;
|
||||
@@ -161,17 +166,16 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
* queued for processing to extract the target channel samples, process them for frequency correction and/or
|
||||
* channel aggregation, and dispatch the results to the downstream sample listener/consumer.
|
||||
*
|
||||
* @param channelResultsBuffer containing an array of polyphase channelizer outputs.
|
||||
* @param channelResultsList containing a list of polyphase channelizer output arrays.
|
||||
*/
|
||||
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
|
||||
public void receiveChannelResults(List<float[]> channelResultsList)
|
||||
{
|
||||
if(mReplacementPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
swapOutputProcessor();
|
||||
}
|
||||
|
||||
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,13 +202,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
if(mReusableComplexBufferAssembler != null)
|
||||
{
|
||||
mReusableComplexBufferAssembler.dispose();
|
||||
}
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.setListener(null);
|
||||
mPolyphaseChannelOutputProcessor.dispose();
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
*/
|
||||
protected void setChannelFrequencyCorrection(long value)
|
||||
{
|
||||
//TODO: push this frequency correction down to the output processor ...
|
||||
mChannelFrequencyCorrection = value;
|
||||
updateFrequencyOffset();
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
||||
@@ -242,12 +243,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
setChannelFrequencyCorrection(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processSamples()
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
|
||||
* within the channel
|
||||
@@ -273,43 +268,4 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
||||
getTunerChannel().getFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
|
||||
*
|
||||
* @param sampleRate for the final output channel rate
|
||||
* @param passFrequency for half of the desired channel rate
|
||||
* @param stopFrequency for the attenuated band
|
||||
* @return a newly designed filter or a previously designed (cached) filter
|
||||
* @throws FilterDesignException
|
||||
*/
|
||||
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
|
||||
{
|
||||
//Use existing filter if we've already designed one
|
||||
float[] filter = sLowPassFilters.get((int) sampleRate);
|
||||
if(filter != null)
|
||||
{
|
||||
return filter;
|
||||
}
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(sampleRate)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(passFrequency)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(stopFrequency)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
//This will throw an exception if the filter cannot be designed
|
||||
float[] taps = designer.getImpulseResponse();
|
||||
|
||||
sLowPassFilters.put((int)sampleRate, taps);
|
||||
|
||||
return taps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
@@ -45,8 +47,6 @@ import org.slf4j.LoggerFactory;
|
||||
public class TwoChannelSynthesizerM2
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelSynthesizerM2.class);
|
||||
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Two Channel Synthesizer M2");
|
||||
private float[] mSerpentineDataBuffer;
|
||||
private float[] mIQInterleavedFilter;
|
||||
private float[] mFilterVectorProduct;
|
||||
@@ -87,28 +87,30 @@ public class TwoChannelSynthesizerM2
|
||||
* @param channelBuffer2 input channel
|
||||
* @return synthesized channel results of the same length as both channel 1/2 input arrays.
|
||||
*/
|
||||
public ReusableComplexBuffer process(ReusableComplexBuffer channelBuffer1, ReusableComplexBuffer channelBuffer2)
|
||||
public ComplexSamples process(ComplexSamples channelBuffer1, ComplexSamples channelBuffer2)
|
||||
{
|
||||
if(channelBuffer1.getSamples().length != channelBuffer1.getSamples().length)
|
||||
if(channelBuffer1.i().length != channelBuffer1.i().length)
|
||||
{
|
||||
throw new IllegalArgumentException("Channel 1 and 2 array length must be equal");
|
||||
}
|
||||
|
||||
float[] channel1 = channelBuffer1.getSamples();
|
||||
float[] channel2 = channelBuffer2.getSamples();
|
||||
float[] iCh1 = channelBuffer1.i();
|
||||
float[] qCh1 = channelBuffer1.q();
|
||||
float[] iCh2 = channelBuffer2.i();
|
||||
float[] qCh2 = channelBuffer2.q();
|
||||
|
||||
ReusableComplexBuffer synthesizedComplexBuffer = mReusableComplexBufferQueue.getBuffer(channel1.length);
|
||||
float[] i = new float[iCh1.length];
|
||||
float[] q = new float[qCh1.length];
|
||||
|
||||
float[] output = synthesizedComplexBuffer.getSamples();
|
||||
float[] IFFTBuffer = new float[4];
|
||||
|
||||
for(int x = 0; x < channel1.length; x += 2)
|
||||
for(int x = 0; x < iCh1.length; x++)
|
||||
{
|
||||
//Load samples from each channel into buffer for IFFT
|
||||
IFFTBuffer[0] = channel1[x]; //i
|
||||
IFFTBuffer[1] = channel1[x + 1]; //q
|
||||
IFFTBuffer[2] = channel2[x]; //i
|
||||
IFFTBuffer[3] = channel2[x + 1]; //q
|
||||
IFFTBuffer[0] = iCh1[x]; //i
|
||||
IFFTBuffer[1] = qCh1[x]; //q
|
||||
IFFTBuffer[2] = iCh2[x]; //i
|
||||
IFFTBuffer[3] = qCh2[x]; //q
|
||||
|
||||
//Perform Inverse FFT (IFFT)
|
||||
mFFT.complexInverse(IFFTBuffer, true);
|
||||
@@ -148,16 +150,13 @@ public class TwoChannelSynthesizerM2
|
||||
mQAccumulator += mFilterVectorProduct[y + 1];
|
||||
}
|
||||
|
||||
output[x] = mIAccumulator;
|
||||
output[x + 1] = mQAccumulator;
|
||||
i[x] = mIAccumulator;
|
||||
q[x] = mQAccumulator;
|
||||
|
||||
mTopBlockFlag = !mTopBlockFlag;
|
||||
}
|
||||
|
||||
channelBuffer1.decrementUserCount();
|
||||
channelBuffer2.decrementUserCount();
|
||||
|
||||
return synthesizedComplexBuffer;
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
@@ -14,35 +14,26 @@
|
||||
*
|
||||
* 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.output;
|
||||
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
|
||||
|
||||
private OverflowableReusableBufferTransferQueue<ReusableChannelResultsBuffer> mChannelResultsQueue;
|
||||
private List<ReusableChannelResultsBuffer> mChannelResultsToProcess = new ArrayList<>();
|
||||
private int mMaxResultsToProcess;
|
||||
|
||||
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
|
||||
protected Listener<ComplexSamples> mComplexSamplesListener;
|
||||
private int mInputChannelCount;
|
||||
private IOscillator mFrequencyCorrectionMixer;
|
||||
private boolean mFrequencyCorrectionEnabled;
|
||||
private double mGain = 1.0;
|
||||
|
||||
/**
|
||||
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
|
||||
@@ -52,21 +43,32 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
||||
* channel sample rate for frequency translation/correction.
|
||||
*/
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain)
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
|
||||
{
|
||||
mInputChannelCount = inputChannelCount;
|
||||
mGain = gain;
|
||||
|
||||
//TODO: swap this out and use the LowPhaseNoiseOscillator
|
||||
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
|
||||
mMaxResultsToProcess = (int)(sampleRate / 10) * 2; //process at 100 millis interval, twice the expected inflow rate
|
||||
|
||||
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
|
||||
mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList());
|
||||
mChannelResultsDispatcher.setListener(floats -> process(floats));
|
||||
}
|
||||
|
||||
protected double getGain()
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
return mGain;
|
||||
mChannelResultsDispatcher.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
mChannelResultsDispatcher.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive the assembled complex sample buffers from this processor.
|
||||
*/
|
||||
@Override
|
||||
public void setListener(Listener<ComplexSamples> listener)
|
||||
{
|
||||
mComplexSamplesListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,97 +79,23 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
if(mChannelResultsQueue != null)
|
||||
{
|
||||
mChannelResultsQueue.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
||||
* @param frequencyOffset in hertz
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyOffset(long frequencyOffset)
|
||||
{
|
||||
mFrequencyCorrectionMixer.setFrequency(frequencyOffset);
|
||||
mFrequencyCorrectionEnabled = (frequencyOffset != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Oscillator/mixer to use for frequency correction against incoming sample stream
|
||||
*/
|
||||
protected IOscillator getFrequencyCorrectionMixer()
|
||||
{
|
||||
return mFrequencyCorrectionMixer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveChannelResults(ReusableChannelResultsBuffer channelResults)
|
||||
public void receiveChannelResults(List<float[]> channelResultsList)
|
||||
{
|
||||
mChannelResultsQueue.offer(channelResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes all enqueued polyphase channelizer results until the internal queue is empty
|
||||
* @param reusableComplexBufferAssembler to receive the processed channel results
|
||||
*/
|
||||
@Override
|
||||
public void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||
{
|
||||
try
|
||||
{
|
||||
int toProcess = mChannelResultsQueue.drainTo(mChannelResultsToProcess, mMaxResultsToProcess);
|
||||
|
||||
if(toProcess > 0)
|
||||
{
|
||||
process(mChannelResultsToProcess, reusableComplexBufferAssembler);
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing polyphase channel samples", throwable);
|
||||
}
|
||||
|
||||
mChannelResultsToProcess.clear();
|
||||
mChannelResultsDispatcher.receive(channelResultsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-class implementation to process one polyphase channelizer result array.
|
||||
* @param channelResults to process
|
||||
* @param reusableComplexBufferAssembler to receive the processed channelizer results
|
||||
*/
|
||||
public abstract void process(List<ReusableChannelResultsBuffer> channelResults,
|
||||
ReusableComplexBufferAssembler reusableComplexBufferAssembler);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the overflow listener to monitor the internal channelizer channel results queue overflow state
|
||||
*/
|
||||
public void setSourceOverflowListener(Source source)
|
||||
{
|
||||
mChannelResultsQueue.setSourceOverflowListener(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the overflow listener from monitoring the internal channelizer channel results queue overflow state
|
||||
*/
|
||||
public void removeOverflowListener(IOverflowListener listener)
|
||||
{
|
||||
mChannelResultsQueue.setOverflowListener(null);
|
||||
}
|
||||
public abstract void process(List<float[]> channelResults);
|
||||
|
||||
@Override
|
||||
public int getInputChannelCount()
|
||||
{
|
||||
return mInputChannelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel has a frequency correction offset
|
||||
*/
|
||||
public boolean hasFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrectionEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
@@ -14,30 +14,41 @@
|
||||
*
|
||||
* 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.output;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IPolyphaseChannelOutputProcessor
|
||||
{
|
||||
/**
|
||||
* Receive and enqueue output results from the polyphase analysis channelizer
|
||||
* @param channelResultsBuffer to enqueue
|
||||
* Start processing channel results
|
||||
*/
|
||||
void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer);
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Process the channel output channel results queue and deliver the output to the listener
|
||||
*
|
||||
* @param reusableComplexBufferAssembler to receive the processed channel results
|
||||
* Stop processing channel results.
|
||||
*/
|
||||
void processChannelResults(ReusableComplexBufferAssembler reusableComplexBufferAssembler);
|
||||
void stop();
|
||||
/**
|
||||
* Receive and enqueue output results from the polyphase analysis channelizer
|
||||
* @param channelResults to enqueue
|
||||
*/
|
||||
void receiveChannelResults(List<float[]> channelResults);
|
||||
|
||||
/**
|
||||
* Listener to receive assembled complex samples buffers
|
||||
*/
|
||||
void setListener(Listener<ComplexSamples> listener);
|
||||
|
||||
// /**
|
||||
// * Process the channel output channel results queue and deliver the output to the listener
|
||||
// */
|
||||
// void processChannelResults();
|
||||
|
||||
/**
|
||||
* Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset
|
||||
@@ -49,13 +60,12 @@ public interface IPolyphaseChannelOutputProcessor
|
||||
|
||||
/**
|
||||
* Indicates the number of input channels processed by this output processor
|
||||
* @return
|
||||
*/
|
||||
int getInputChannelCount();
|
||||
|
||||
/**
|
||||
* Updates the input polyphase channel index(es) used by this output processor
|
||||
* @param indexes
|
||||
* @param indexes for the channel
|
||||
*/
|
||||
void setPolyphaseChannelIndices(List<Integer> indexes);
|
||||
|
||||
@@ -66,16 +76,9 @@ public interface IPolyphaseChannelOutputProcessor
|
||||
|
||||
/**
|
||||
* Updates the synthesis filter taps for this output processor
|
||||
* @param filter
|
||||
* @param filter for the output processor
|
||||
*/
|
||||
void setSynthesisFilter(float[] filter);
|
||||
|
||||
/**
|
||||
* Sets the listener to receive buffer overflow notifications
|
||||
* @param source to receive overflow notifications
|
||||
*/
|
||||
void setSourceOverflowListener(Source source);
|
||||
|
||||
|
||||
void dispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.dsp.gain.complex.ComplexGain;
|
||||
import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory;
|
||||
import io.github.dsheirer.dsp.mixer.ComplexMixer;
|
||||
import io.github.dsheirer.dsp.mixer.ComplexMixerFactory;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
/**
|
||||
* Sample assembler for a channel output processor that incorporates a mixer to perform final frequency
|
||||
* translation on an extracted channel.
|
||||
*/
|
||||
public abstract class MixerAssembler
|
||||
{
|
||||
protected static final int BUFFER_SIZE = 2048;
|
||||
|
||||
private ComplexMixer mMixer = ComplexMixerFactory.getMixer(0, 0);
|
||||
private ComplexGain mGain;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param gain to apply to fully assembled buffers.
|
||||
*/
|
||||
protected MixerAssembler(float gain)
|
||||
{
|
||||
mGain = ComplexGainFactory.getComplexGain(gain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complex samples mixer for this assembler
|
||||
*/
|
||||
protected ComplexMixer getMixer()
|
||||
{
|
||||
return mMixer;
|
||||
}
|
||||
|
||||
protected ComplexGain getGain()
|
||||
{
|
||||
return mGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this assembler has a buffer ready to dispatch to consumers.
|
||||
*/
|
||||
public abstract boolean hasBuffer();
|
||||
|
||||
/**
|
||||
* Access the fully assembled buffer when hasBuffer() indicates that a buffer is ready.
|
||||
* @return true if a full buffer is assembled and ready.
|
||||
*/
|
||||
public abstract ComplexSamples getBuffer();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
/**
|
||||
* Sample assembler to extract a single channel from the channel results array of a polyphase channel results array.
|
||||
*/
|
||||
public class OneChannelMixerAssembler extends MixerAssembler
|
||||
{
|
||||
private SampleAssembler mSampleAssembler = new SampleAssembler(BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param gain to apply to fully assembled buffers.
|
||||
*/
|
||||
public OneChannelMixerAssembler(float gain)
|
||||
{
|
||||
super(gain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a complex I & Q sample pair.
|
||||
* @param i inphase
|
||||
* @param q quadrature
|
||||
*/
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
mSampleAssembler.receive(i, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there is a fully assembled buffer ready.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasBuffer()
|
||||
{
|
||||
return mSampleAssembler.hasBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the assembled buffer. Frequency offset and gain are applied to the returned buffer.
|
||||
* @return assembled buffer.
|
||||
*/
|
||||
@Override
|
||||
public ComplexSamples getBuffer()
|
||||
{
|
||||
ComplexSamples buffer = mSampleAssembler.getBufferAndReset();
|
||||
|
||||
if(getMixer().hasFrequency())
|
||||
{
|
||||
buffer = getMixer().mix(buffer);
|
||||
}
|
||||
|
||||
buffer = getGain().apply(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
@@ -14,13 +14,11 @@
|
||||
*
|
||||
* 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.output;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -29,7 +27,7 @@ import java.util.List;
|
||||
public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(OneChannelOutputProcessor.class);
|
||||
|
||||
private final OneChannelMixerAssembler mMixerAssembler;
|
||||
private int mChannelOffset;
|
||||
|
||||
/**
|
||||
@@ -38,12 +36,14 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
*
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing a single channel index.
|
||||
* @param gain value to apply. Typically this is the same as the channelizer's channel count.
|
||||
* @param gain value to apply. This is typically the same as the channelizer's channel count.
|
||||
*/
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, double gain)
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain)
|
||||
{
|
||||
super(1, sampleRate, gain);
|
||||
super(1, sampleRate);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mMixerAssembler = new OneChannelMixerAssembler(gain);
|
||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,7 +62,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
if(indexes.size() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Single channel output processor requires a single index to " +
|
||||
"process - provided indexes " + indexes.toString());
|
||||
"process - provided indexes " + indexes);
|
||||
}
|
||||
|
||||
//Set the channelized output results offset to twice the channel index to account for each channel having
|
||||
@@ -70,38 +70,45 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
mChannelOffset = indexes.get(0) * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyOffset(long frequency)
|
||||
{
|
||||
mMixerAssembler.getMixer().setFrequency(frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the channel from the channel results array, apply frequency translation, and deliver the
|
||||
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
|
||||
* Extract the channel from the channel results array and pass to the assembler. The assembler will
|
||||
* apply frequency translation and gain and indicate when a buffer is fully assembled.
|
||||
*
|
||||
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results
|
||||
* @param lists to process containing a list of a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
*/
|
||||
@Override
|
||||
public void process(List<ReusableChannelResultsBuffer> channelResults,
|
||||
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||
public void process(List<float[]> channelResultsList)
|
||||
{
|
||||
for(ReusableChannelResultsBuffer channelResultsBuffer: channelResults)
|
||||
for(float[] channelResults: channelResultsList)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReusableComplexBuffer channelBuffer = channelResultsBuffer.getChannel(mChannelOffset);
|
||||
mMixerAssembler.receive(channelResults[mChannelOffset], channelResults[mChannelOffset + 1]);
|
||||
|
||||
if(hasFrequencyCorrection())
|
||||
if(mMixerAssembler.hasBuffer())
|
||||
{
|
||||
ComplexSamples buffer = mMixerAssembler.getBuffer();
|
||||
|
||||
if(mComplexSamplesListener != null)
|
||||
{
|
||||
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
|
||||
try
|
||||
{
|
||||
mComplexSamplesListener.receive(buffer);
|
||||
}
|
||||
catch(NullPointerException npe)
|
||||
{
|
||||
//Ignore ... can happen when the listener is nullified on another thread
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error extracting channel samples from one polyphase channel results buffer", e);
|
||||
}
|
||||
}
|
||||
|
||||
channelBuffer.applyGain(getGain());
|
||||
|
||||
reusableComplexBufferAssembler.receive(channelBuffer);
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.error("Error extracting channel samples from polyphase channel results buffer");
|
||||
}
|
||||
|
||||
channelResultsBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
public class SampleAssembler
|
||||
{
|
||||
private final int mBufferSize;
|
||||
private float[] mI;
|
||||
private float[] mQ;
|
||||
private int mPointer = 0;
|
||||
|
||||
/**
|
||||
* Creates a sample assembler to produce buffers of the specified size.
|
||||
* @param bufferSize for a full buffer
|
||||
*/
|
||||
public SampleAssembler(int bufferSize)
|
||||
{
|
||||
mBufferSize = bufferSize;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffer pointer and creates new (empty) I and Q arrays.
|
||||
*/
|
||||
private void reset()
|
||||
{
|
||||
mPointer = 0;
|
||||
mI = new float[mBufferSize];
|
||||
mQ = new float[mBufferSize];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the I & Q samples to this assembler.
|
||||
* @param i inphase sample
|
||||
* @param q quadrature sample.
|
||||
*/
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
if(mPointer >= mBufferSize)
|
||||
{
|
||||
throw new IllegalStateException("Buffer is full. Use flushBuffer() to reset the assembler.");
|
||||
}
|
||||
|
||||
mI[mPointer] = i;
|
||||
mQ[mPointer++] = q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this assembler has a full buffer.
|
||||
* Note: use getBufferAndReset() to access the full buffer.
|
||||
*/
|
||||
public boolean hasBuffer()
|
||||
{
|
||||
return mPointer >= mBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the full buffer from this assembler and resets the assembler to accept more samples.
|
||||
*/
|
||||
public ComplexSamples getBufferAndReset()
|
||||
{
|
||||
ComplexSamples buffer = new ComplexSamples(mI, mQ);
|
||||
reset();
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
|
||||
import io.github.dsheirer.dsp.oscillator.FS4DownConverter;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
/**
|
||||
* Sample assembler to extract a single channel from the channel results array of a polyphase channel results array.
|
||||
*/
|
||||
public class TwoChannelMixerAssembler extends MixerAssembler
|
||||
{
|
||||
private SampleAssembler mChannel1SampleAssembler = new SampleAssembler(BUFFER_SIZE);
|
||||
private SampleAssembler mChannel2SampleAssembler = new SampleAssembler(BUFFER_SIZE);
|
||||
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
||||
private TwoChannelSynthesizerM2 mTwoChannelSynthesizer;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param gain to apply to fully assembled buffers.
|
||||
*/
|
||||
public TwoChannelMixerAssembler(float gain)
|
||||
{
|
||||
super(gain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter coefficients to use for two-channel synthesis
|
||||
* @param filter
|
||||
*/
|
||||
public void setSynthesisFilter(float[] filter)
|
||||
{
|
||||
mTwoChannelSynthesizer = new TwoChannelSynthesizerM2(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a complex I & Q sample pair.
|
||||
* @param iChannel1 inphase
|
||||
* @param qChannel1 quadrature
|
||||
* @param iChannel2 inphase
|
||||
* @param qChannel2 quadrature
|
||||
*/
|
||||
public void receive(float iChannel1, float qChannel1, float iChannel2, float qChannel2)
|
||||
{
|
||||
mChannel1SampleAssembler.receive(iChannel1, qChannel1);
|
||||
mChannel2SampleAssembler.receive(iChannel2, qChannel2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there is a fully assembled buffer ready.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasBuffer()
|
||||
{
|
||||
//Both channel 1 and channel 2 assemblers work in parallel, so we only need to check one of them.
|
||||
return mChannel1SampleAssembler.hasBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the assembled buffer. Frequency offset and gain are applied to the returned buffer.
|
||||
* @return assembled buffer.
|
||||
*/
|
||||
@Override
|
||||
public ComplexSamples getBuffer()
|
||||
{
|
||||
ComplexSamples channel1 = mChannel1SampleAssembler.getBufferAndReset();
|
||||
ComplexSamples channel2 = mChannel2SampleAssembler.getBufferAndReset();
|
||||
|
||||
//Synthesize two channels into one, reducing sample rate by 2 so it's same as single channel rate
|
||||
ComplexSamples buffer = mTwoChannelSynthesizer.process(channel1, channel2);
|
||||
|
||||
//Mix to baseband
|
||||
buffer = mFS4DownConverter.mixComplex(buffer);
|
||||
|
||||
//Apply frequency correction if necessary
|
||||
if(getMixer().hasFrequency())
|
||||
{
|
||||
buffer = getMixer().mix(buffer);
|
||||
}
|
||||
|
||||
//Apply gain
|
||||
buffer = getGain().apply(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,33 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
|
||||
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
||||
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
||||
private TwoChannelSynthesizerM2 mSynthesizer;
|
||||
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
|
||||
private TwoChannelMixerAssembler mMixerAssembler;
|
||||
private int mChannelOffset1;
|
||||
private int mChannelOffset2;
|
||||
|
||||
@@ -40,12 +39,14 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* @param channelIndexes containing two channel indices.
|
||||
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
|
||||
*/
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, double gain)
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain)
|
||||
{
|
||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||
//after synthesizing both input channels
|
||||
super(2, sampleRate, gain);
|
||||
super(2, sampleRate);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mMixerAssembler = new TwoChannelMixerAssembler(gain);
|
||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||
setSynthesisFilter(filter);
|
||||
}
|
||||
|
||||
@@ -57,14 +58,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
@Override
|
||||
public void setFrequencyOffset(long frequencyOffset)
|
||||
{
|
||||
super.setFrequencyOffset(frequencyOffset);
|
||||
mMixerAssembler.getMixer().setFrequency(frequencyOffset);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSynthesisFilter(float[] filter)
|
||||
{
|
||||
mSynthesizer = new TwoChannelSynthesizerM2(filter);
|
||||
mMixerAssembler.setSynthesisFilter(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
if(indexes.size() != 2)
|
||||
{
|
||||
throw new IllegalArgumentException("Double channel output processor requires two indexes to " +
|
||||
"process - provided indexes " + indexes.toString());
|
||||
"process - provided indexes " + indexes);
|
||||
}
|
||||
|
||||
//Set the channelized output results offsets to twice the channel index to account for each channel having
|
||||
@@ -91,32 +91,36 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* Extract the channel from the channel results array, apply frequency translation, and deliver the
|
||||
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
|
||||
*
|
||||
* @param channelResultsBuffers to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param reusableComplexBufferAssembler to receive the extracted, frequency-translated channel results
|
||||
* @param lists to process containing a list of a list of an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
*/
|
||||
@Override
|
||||
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
|
||||
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
|
||||
public void process(List<float[]> channelResultsList)
|
||||
{
|
||||
for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
|
||||
for(float[] channelResults : channelResultsList)
|
||||
{
|
||||
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
|
||||
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
|
||||
mMixerAssembler.receive(channelResults[mChannelOffset1], channelResults[mChannelOffset1 + 1],
|
||||
channelResults[mChannelOffset2], channelResults[mChannelOffset2 + 1]);
|
||||
|
||||
//Join the two channels using the synthesizer
|
||||
ReusableComplexBuffer synthesized = mSynthesizer.process(channel1, channel2);
|
||||
if(mMixerAssembler.hasBuffer())
|
||||
{
|
||||
ComplexSamples buffer = mMixerAssembler.getBuffer();
|
||||
|
||||
//The synthesized channels are centered at +FS/4 ... downconvert to center the spectrum
|
||||
mFS4DownConverter.mixComplex(synthesized.getSamples());
|
||||
|
||||
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
|
||||
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
|
||||
|
||||
synthesized.applyGain(getGain());
|
||||
|
||||
reusableComplexBufferAssembler.receive(synthesized);
|
||||
|
||||
buffer.decrementUserCount();
|
||||
if(mComplexSamplesListener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mComplexSamplesListener.receive(buffer);
|
||||
}
|
||||
catch(NullPointerException npe)
|
||||
{
|
||||
//Ignore ... can happen when the listener is nullified on another thread
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error extracting channel samples from two polyphase channel results buffer", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,456 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.filter.cic;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||
import io.github.dsheirer.sample.decimator.ComplexDecimator;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.math3.primes.Primes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This
|
||||
* filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are
|
||||
* cascaded to achieve the overall decimation rate.
|
||||
*
|
||||
* This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
|
||||
* adding additional prime factors to the PRIMES array.
|
||||
*/
|
||||
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class);
|
||||
|
||||
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
|
||||
private static Map<Integer,List<Integer>> sPrimeFactors = new HashMap();
|
||||
|
||||
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
|
||||
private DecimatingStage mFirstDecimatingStage;
|
||||
private Output mOutput;
|
||||
|
||||
/**
|
||||
* Constructs a new decimation filter.
|
||||
*
|
||||
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
|
||||
* pass band might be:
|
||||
* sampleRate = 2,400,000.0
|
||||
* decimation = 96
|
||||
* passFrequency = 6250 (1/4 of the channel rate)
|
||||
* stopFrequency = 7000
|
||||
*
|
||||
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
|
||||
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
|
||||
*
|
||||
* @param sampleRate of the input sample stream
|
||||
* @param decimation - overall decimation rate
|
||||
* @param passFrequency for the final cleanup filter
|
||||
* @param stopFrequency for the final cleanup filter
|
||||
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
|
||||
* and specified pass/stop frequencies.
|
||||
*/
|
||||
public ComplexPrimeCICDecimate(double sampleRate, int decimation, double passFrequency, double stopFrequency)
|
||||
throws FilterDesignException
|
||||
{
|
||||
List<Integer> stageSizes = getPrimeFactors(decimation);
|
||||
|
||||
for(int x = 0; x < stageSizes.size(); x++)
|
||||
{
|
||||
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1);
|
||||
|
||||
mDecimatingStages.add(stage);
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
/* Reference to first stage -- will receive all samples */
|
||||
mFirstDecimatingStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Wire the current stage to the previous stage */
|
||||
mDecimatingStages.get(x - 1).setListener(stage);
|
||||
}
|
||||
}
|
||||
|
||||
double channelRate = sampleRate / (double)decimation;
|
||||
|
||||
mOutput = new Output(channelRate, passFrequency, stopFrequency);
|
||||
|
||||
mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(DecimatingStage stage : mDecimatingStages)
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mDecimatingStages.clear();
|
||||
mDecimatingStages = null;
|
||||
mFirstDecimatingStage = null;
|
||||
|
||||
mOutput.dispose();
|
||||
mOutput = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive the output of this CIC decimation filter
|
||||
*/
|
||||
public void setListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mOutput.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listener from output of this CIC decimation filter
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mOutput.removeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the
|
||||
* highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array.
|
||||
*
|
||||
* @param decimation - integral decimation rate
|
||||
* @return - ordered list (smallest to largest) of prime factors
|
||||
*/
|
||||
public static List<Integer> getPrimeFactors(int decimation)
|
||||
{
|
||||
List<Integer> primeFactors = sPrimeFactors.get(decimation);
|
||||
if(primeFactors != null)
|
||||
{
|
||||
return primeFactors;
|
||||
}
|
||||
|
||||
primeFactors = Primes.primeFactors(decimation);
|
||||
|
||||
sPrimeFactors.put(decimation, primeFactors);
|
||||
|
||||
return primeFactors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer buffer)
|
||||
{
|
||||
if(mFirstDecimatingStage != null)
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimating stage combines multiple CIC stages with a decimator. The
|
||||
* number of stages is indicated by the order value and the size indicates
|
||||
* the decimation rate of this stage.
|
||||
*/
|
||||
public class DecimatingStage implements ComplexSampleListener
|
||||
{
|
||||
private List<Stage> mStages = new ArrayList<Stage>();
|
||||
private Stage mFirstStage;
|
||||
private ComplexDecimator mDecimator;
|
||||
|
||||
public DecimatingStage(int size, int order)
|
||||
{
|
||||
for(int x = 0; x < order; x++)
|
||||
{
|
||||
Stage stage;
|
||||
|
||||
if(size == 2)
|
||||
{
|
||||
stage = new TwoStage();
|
||||
}
|
||||
else
|
||||
{
|
||||
stage = new Stage(size);
|
||||
}
|
||||
|
||||
mStages.add(stage);
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
mFirstStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
mStages.get(x - 1).setListener(stage);
|
||||
}
|
||||
}
|
||||
|
||||
mDecimator = new ComplexDecimator(size);
|
||||
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(Stage stage : mStages)
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mStages.clear();
|
||||
mDecimator.dispose();
|
||||
mDecimator = null;
|
||||
mFirstStage = null;
|
||||
mStages = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
mFirstStage.receive(i, q);
|
||||
}
|
||||
|
||||
public void setListener(ComplexSampleListener listener)
|
||||
{
|
||||
mDecimator.setListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
|
||||
* the stage so that stage size has essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class Stage implements ComplexSampleListener
|
||||
{
|
||||
protected ComplexSampleListener mListener;
|
||||
|
||||
private float[] mISamples;
|
||||
private float[] mQSamples;
|
||||
|
||||
protected float mISum;
|
||||
protected float mQSum;
|
||||
|
||||
private int mSamplePointer = 0;
|
||||
private int mSize;
|
||||
|
||||
protected float mGain;
|
||||
|
||||
protected Stage()
|
||||
{
|
||||
}
|
||||
|
||||
public Stage(int size)
|
||||
{
|
||||
mSize = size - 1;
|
||||
|
||||
mISamples = new float[mSize];
|
||||
mQSamples = new float[mSize];
|
||||
|
||||
float baseGain = 1.0f / (float)size;
|
||||
mGain = baseGain * (1.0f / (1.0f - baseGain));
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
/* Subtract the oldest sample and add in the newest sample */
|
||||
mISum = mISum - mISamples[mSamplePointer] + i;
|
||||
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
||||
|
||||
/* Overwrite the oldest sample with the newest */
|
||||
mISamples[mSamplePointer] = i;
|
||||
mQSamples[mSamplePointer] = q;
|
||||
|
||||
mSamplePointer++;
|
||||
|
||||
if(mSamplePointer >= mSize)
|
||||
{
|
||||
mSamplePointer = 0;
|
||||
}
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive((mISum * mGain), (mQSum * mGain));
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(ComplexSampleListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
|
||||
*/
|
||||
public class TwoStage extends Stage
|
||||
{
|
||||
public TwoStage()
|
||||
{
|
||||
mGain = 0.5f;
|
||||
}
|
||||
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain));
|
||||
}
|
||||
|
||||
mISum = i;
|
||||
mQSum = q;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction and cleanup filter and broadcast
|
||||
* to registered listener.
|
||||
*/
|
||||
public class Output implements ComplexSampleListener
|
||||
{
|
||||
/* Decimated output buffers will contain 1024 complex samples */
|
||||
private ReusableComplexBufferAssembler mBufferAssembler;
|
||||
private ComplexFIRFilter2 mLowPassFilter;
|
||||
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
|
||||
|
||||
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
|
||||
{
|
||||
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
|
||||
|
||||
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
|
||||
float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
|
||||
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
|
||||
|
||||
mBufferAssembler.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
if(mReusableComplexBufferListener != null)
|
||||
{
|
||||
ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer);
|
||||
mReusableComplexBufferListener.receive(filteredBuffer);
|
||||
// mReusableComplexBufferListener.receive(reusableComplexBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBufferAssembler.dispose();
|
||||
mLowPassFilter.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for receiving CIC decimated output samples
|
||||
*/
|
||||
@Override
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
mBufferAssembler.receive(inphase, quadrature);
|
||||
}
|
||||
|
||||
public void setListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mReusableComplexBufferListener = listener;
|
||||
|
||||
if(mReusableComplexBufferListener == null)
|
||||
{
|
||||
removeListener();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener()
|
||||
{
|
||||
mReusableComplexBufferListener = new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
//empty receiver
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
|
||||
*
|
||||
* @param sampleRate for the final output channel rate
|
||||
* @param passFrequency for half of the desired channel rate
|
||||
* @param stopFrequency for the attenuated band
|
||||
* @return a newly designed filter or a previously designed (cached) filter
|
||||
* @throws FilterDesignException
|
||||
*/
|
||||
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
|
||||
{
|
||||
//Use existing filter if we've already designed one
|
||||
float[] existingFilter = sLowPassFilters.get((int) sampleRate);
|
||||
if(existingFilter != null)
|
||||
{
|
||||
return existingFilter;
|
||||
}
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(sampleRate)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(passFrequency)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(stopFrequency)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
//This will throw an exception if the filter cannot be designed
|
||||
float[] taps = designer.getImpulseResponse();
|
||||
|
||||
sLowPassFilters.put((int)sampleRate, taps);
|
||||
|
||||
return taps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.filter.cic;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import org.apache.commons.math3.primes.Primes;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This
|
||||
* filter contains multiple decimating stages each with a prime factor decimation rate. Multiple stages are
|
||||
* cascaded to achieve the overall decimation rate.
|
||||
*
|
||||
* This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
|
||||
* adding additional prime factors to the PRIMES array.
|
||||
*/
|
||||
public class PrimeCicDecimationFilter
|
||||
{
|
||||
private Stage mFirstStage;
|
||||
|
||||
/**
|
||||
* Constructs a new decimation filter.
|
||||
*
|
||||
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
|
||||
* pass band might be:
|
||||
* sampleRate = 2,400,000.0
|
||||
* decimation = 96
|
||||
* passFrequency = 6250 (1/4 of the channel rate)
|
||||
* stopFrequency = 7000
|
||||
*
|
||||
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
|
||||
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
|
||||
*
|
||||
* @param sampleRate of the input sample stream
|
||||
* @param decimation - overall decimation rate
|
||||
* @param passFrequency for the final cleanup filter
|
||||
* @param stopFrequency for the final cleanup filter
|
||||
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
|
||||
* and specified pass/stop frequencies.
|
||||
*/
|
||||
public PrimeCicDecimationFilter(double sampleRate, int decimation, double passFrequency, double stopFrequency)
|
||||
throws FilterDesignException
|
||||
{
|
||||
List<Integer> stageSizes = Primes.primeFactors(decimation);
|
||||
|
||||
Stage stage = null;
|
||||
|
||||
for(int x = 0; x < stageSizes.size(); x++)
|
||||
{
|
||||
Stage nextStage = new DecimatingStage(stageSizes.get(x), 1);
|
||||
|
||||
if(stage != null)
|
||||
{
|
||||
stage.setChild(nextStage);
|
||||
}
|
||||
|
||||
stage = nextStage;
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
/* Reference to first stage -- will receive all samples */
|
||||
mFirstStage = stage;
|
||||
}
|
||||
}
|
||||
|
||||
//stage should reference the last stage that was created at this point. Create an
|
||||
//output and chain it to the final stage.
|
||||
if(stage != null)
|
||||
{
|
||||
double channelRate = sampleRate / (double)decimation;
|
||||
Stage output = new Output(channelRate, passFrequency, stopFrequency);
|
||||
stage.setChild(output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates the sample array.
|
||||
* @param samples to decimate
|
||||
* @return decimated samples
|
||||
*/
|
||||
public float[] decimate(float[] samples)
|
||||
{
|
||||
return mFirstStage.process(samples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base stage for all decimating and non-decimating stages
|
||||
*/
|
||||
public abstract class Stage
|
||||
{
|
||||
private Stage mChild;
|
||||
|
||||
/**
|
||||
* Sets the child stage to follow this stage
|
||||
* @param stage child
|
||||
*/
|
||||
public void setChild(Stage stage)
|
||||
{
|
||||
mChild = stage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this stage has an optional child stage that should process the samples
|
||||
* after this stage processes those samples.
|
||||
*/
|
||||
protected boolean hasChild()
|
||||
{
|
||||
return mChild != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional child stage that should process samples after this stage.
|
||||
*/
|
||||
protected Stage getChild()
|
||||
{
|
||||
return mChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the samples. Note: original argument array will be reused if/when possible.
|
||||
* @param samples to process
|
||||
* @return processed samples.
|
||||
*/
|
||||
public abstract float[] process(float[] samples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimating stage chains multiple non-decimating stages with a decimator. The
|
||||
* number of stages in the chain is dictated by the order value and the size indicates
|
||||
* the decimation rate of this stage.
|
||||
*/
|
||||
public class DecimatingStage extends Stage
|
||||
{
|
||||
private Stage mFirstStage;
|
||||
private Stage mDecimator;
|
||||
|
||||
/**
|
||||
* Constructs a decimating stage
|
||||
* @param size
|
||||
* @param order
|
||||
*/
|
||||
public DecimatingStage(int size, int order)
|
||||
{
|
||||
Stage stage = null;
|
||||
|
||||
for(int x = 0; x < order; x++)
|
||||
{
|
||||
Stage nextStage;
|
||||
|
||||
if(size == 2)
|
||||
{
|
||||
nextStage = new NoDecimateTwoStage();
|
||||
}
|
||||
else
|
||||
{
|
||||
nextStage = new NoDecimateStage(size);
|
||||
}
|
||||
|
||||
//Chain each stage together
|
||||
if(stage != null)
|
||||
{
|
||||
stage.setChild(nextStage);
|
||||
}
|
||||
|
||||
stage = nextStage;
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
mFirstStage = stage;
|
||||
}
|
||||
}
|
||||
|
||||
mDecimator = new Decimator(size);
|
||||
|
||||
//Add a decimator to the end of the stage chain.
|
||||
if(stage != null)
|
||||
{
|
||||
stage.setChild(mDecimator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
if(hasChild())
|
||||
{
|
||||
return getChild().process(mFirstStage.process(samples));
|
||||
}
|
||||
|
||||
return mFirstStage.process(samples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behavior since this stage is a chain of stages and we want any external
|
||||
* child to be added to the final stage in the chain, which is the decimator.
|
||||
* @param stage child to add
|
||||
*/
|
||||
@Override
|
||||
public void setChild(Stage stage)
|
||||
{
|
||||
mDecimator.setChild(stage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates samples of float arrays.
|
||||
*/
|
||||
public class Decimator extends Stage
|
||||
{
|
||||
private float[] mResidual;
|
||||
private int mDecimationFactor;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param decimationFactor ratio of 1:decimate.
|
||||
*/
|
||||
public Decimator(int decimationFactor)
|
||||
{
|
||||
mDecimationFactor = decimationFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates the sample array by combining the array with any residual from the previous decimation
|
||||
* operation, decimating, and then storing any residual samples leftover from the decimation.
|
||||
* @param samples to decimate
|
||||
* @return decimated samples array.
|
||||
*/
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
if(mResidual != null)
|
||||
{
|
||||
float[] expanded = new float[mResidual.length + samples.length];
|
||||
System.arraycopy(mResidual, 0, expanded, 0, mResidual.length);
|
||||
System.arraycopy(samples, 0, expanded, mResidual.length, samples.length);
|
||||
samples = expanded;
|
||||
mResidual = null;
|
||||
}
|
||||
|
||||
float[] decimated = new float[samples.length / mDecimationFactor];
|
||||
|
||||
for(int x = 0; x < decimated.length; x++)
|
||||
{
|
||||
decimated[x] = samples[x * mDecimationFactor];
|
||||
}
|
||||
|
||||
int residual = samples.length % mDecimationFactor;
|
||||
|
||||
if(residual > 0)
|
||||
{
|
||||
mResidual = Arrays.copyOfRange(samples, samples.length - residual, samples.length);
|
||||
}
|
||||
|
||||
if(hasChild())
|
||||
{
|
||||
return getChild().process(decimated);
|
||||
}
|
||||
|
||||
return decimated;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single, non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
|
||||
* the stage so that stage size has essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class NoDecimateStage extends Stage
|
||||
{
|
||||
private float[] mSampleBuffer;
|
||||
private int mSamplePointer = 0;
|
||||
private int mSize;
|
||||
protected float mSum;
|
||||
protected float mGain;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param size of the stage
|
||||
*/
|
||||
public NoDecimateStage(int size)
|
||||
{
|
||||
mSize = size - 1;
|
||||
mSampleBuffer = new float[mSize];
|
||||
float baseGain = 1.0f / (float)size;
|
||||
mGain = baseGain * (1.0f / (1.0f - baseGain));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate constructor that specifies gain only.
|
||||
* @param gain to apply
|
||||
*/
|
||||
protected NoDecimateStage(float gain)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
float sum = mSum;
|
||||
float gain = mGain;
|
||||
float size = mSize;
|
||||
int pointer = mSamplePointer;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
/* Subtract the oldest sample and add in the newest sample */
|
||||
sum -= (mSampleBuffer[pointer] + samples[x]);
|
||||
|
||||
/* Overwrite the oldest sample with the newest */
|
||||
mSampleBuffer[pointer] = samples[x];
|
||||
pointer++;
|
||||
pointer %= size;
|
||||
samples[x] = (sum * gain);
|
||||
}
|
||||
|
||||
mSum = sum;
|
||||
mSamplePointer = pointer;
|
||||
|
||||
if(hasChild())
|
||||
{
|
||||
return getChild().process(samples);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
|
||||
*/
|
||||
public class NoDecimateTwoStage extends NoDecimateStage
|
||||
{
|
||||
public NoDecimateTwoStage()
|
||||
{
|
||||
//Use gain of 1/2 or 0.5
|
||||
super(0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
float previousSample = mSum;
|
||||
float temp;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
//Calculate average of the current and previous samples
|
||||
temp = (previousSample + samples[x]) * 0.5f;
|
||||
previousSample = samples[x];
|
||||
samples[x] = temp;
|
||||
}
|
||||
|
||||
mSum = previousSample;
|
||||
|
||||
if(hasChild())
|
||||
{
|
||||
return getChild().process(samples);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction and cleanup filter and broadcast
|
||||
* to registered listener.
|
||||
*/
|
||||
public class Output extends Stage
|
||||
{
|
||||
private IRealFilter mFilter;
|
||||
|
||||
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
|
||||
{
|
||||
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
|
||||
float[] coefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
|
||||
mFilter = FilterFactory.getRealFilter(coefficients);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
if(hasChild())
|
||||
{
|
||||
return getChild().process(mFilter.filter(samples));
|
||||
}
|
||||
|
||||
return mFilter.filter(samples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
|
||||
*
|
||||
* @param sampleRate for the final output channel rate
|
||||
* @param passFrequency for half of the desired channel rate
|
||||
* @param stopFrequency for the attenuated band
|
||||
* @return a newly designed filter or a previously designed (cached) filter
|
||||
* @throws FilterDesignException
|
||||
*/
|
||||
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(sampleRate)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(passFrequency)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(stopFrequency)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
//This will throw an exception if the filter cannot be designed
|
||||
return designer.getImpulseResponse();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
double sampleRate = 25.0;
|
||||
int decimation = 5;
|
||||
double finalRate = sampleRate / decimation;
|
||||
IRealOscillator oscillator = OscillatorFactory.getRealOscillator(1.0, sampleRate);
|
||||
|
||||
try
|
||||
{
|
||||
PrimeCicDecimationFilter filter = new PrimeCicDecimationFilter(sampleRate, decimation, 1.5, 2.0);
|
||||
|
||||
for(int x = 0; x < 20; x++)
|
||||
{
|
||||
float[] samples = oscillator.generate(5000);
|
||||
float[] decimated = filter.decimate(samples);
|
||||
System.out.println(Arrays.toString(decimated));
|
||||
}
|
||||
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
fde.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.cic;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.decimator.RealDecimator;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.math3.primes.Primes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RealPrimeCICDecimate
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealPrimeCICDecimate.class);
|
||||
|
||||
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
|
||||
private DecimatingStage mFirstDecimatingStage;
|
||||
private Output mOutput;
|
||||
|
||||
private int mOutputBufferSize = 2048;
|
||||
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
||||
* decimated float sample output.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal
|
||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
||||
* decimating stages each with a prime factor decimation rate. Multiple
|
||||
* stages are cascaded to achieve the overall decimation rate.
|
||||
*
|
||||
* This filter supports a maximum decimation rate of 700. This filter can
|
||||
* be adapted to higher decimation rates by adding additional prime factors
|
||||
* to the PRIMES array.
|
||||
*
|
||||
* @param decimation - overall decimation rate
|
||||
* @param order - filter order
|
||||
*/
|
||||
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType)
|
||||
{
|
||||
this(decimation, order, passFrequency, attenuation, windowType, 2048);
|
||||
}
|
||||
|
||||
public RealPrimeCICDecimate(int decimation, int order, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
|
||||
{
|
||||
Validate.isTrue(decimation <= 700);
|
||||
|
||||
mOutputBufferSize = outputBufferSize;
|
||||
|
||||
List<Integer> stageSizes = Primes.primeFactors(decimation);
|
||||
|
||||
for(int x = 0; x < stageSizes.size(); x++)
|
||||
{
|
||||
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
|
||||
|
||||
mDecimatingStages.add(stage);
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
/* Reference to first stage -- will receive all samples */
|
||||
mFirstDecimatingStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Wire the current stage to the previous stage */
|
||||
mDecimatingStages.get(x - 1).setListener(stage);
|
||||
}
|
||||
}
|
||||
|
||||
mOutput = new Output(48000, passFrequency, attenuation, windowType,
|
||||
outputBufferSize);
|
||||
|
||||
mDecimatingStages.get(mDecimatingStages.size() - 1)
|
||||
.setListener(mOutput);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(DecimatingStage stage : mDecimatingStages)
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mDecimatingStages.clear();
|
||||
mDecimatingStages = null;
|
||||
mFirstDecimatingStage = null;
|
||||
|
||||
mOutput.dispose();
|
||||
mOutput = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive the output of this CIC decimation filter
|
||||
*/
|
||||
public void setListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mOutput.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listener from output of this CIC decimation filter
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mOutput.removeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for receiving real buffers.
|
||||
*/
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
if(mFirstDecimatingStage != null)
|
||||
{
|
||||
for(float sample : buffer.getSamples())
|
||||
{
|
||||
mFirstDecimatingStage.receive(sample);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimating stage combines multiple CIC stages with a decimator. The
|
||||
* number of stages is indicated by the order value and the size indicates
|
||||
* the decimation rate of this stage.
|
||||
*/
|
||||
public class DecimatingStage implements RealSampleListener
|
||||
{
|
||||
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
||||
private Stage mFirstStage;
|
||||
private RealDecimator mDecimator;
|
||||
|
||||
public DecimatingStage(int size, int order)
|
||||
{
|
||||
for(int x = 0; x < order; x++)
|
||||
{
|
||||
Stage stage;
|
||||
|
||||
if(size == 2)
|
||||
{
|
||||
stage = new TwoStage();
|
||||
}
|
||||
else
|
||||
{
|
||||
stage = new Stage(size);
|
||||
}
|
||||
|
||||
mStages.add(stage);
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
mFirstStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
mStages.get(x - 1).setListener(stage);
|
||||
}
|
||||
}
|
||||
|
||||
mDecimator = new RealDecimator(size);
|
||||
|
||||
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(Stage stage : mStages)
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mStages.clear();
|
||||
mDecimator.dispose();
|
||||
mDecimator = null;
|
||||
mFirstStage = null;
|
||||
mStages = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float sample)
|
||||
{
|
||||
mFirstStage.receive(sample);
|
||||
}
|
||||
|
||||
public void setListener(RealSampleListener listener)
|
||||
{
|
||||
mDecimator.setListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
||||
* running average internally to implement the stage so that stage size has
|
||||
* essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class Stage implements RealSampleListener
|
||||
{
|
||||
protected RealSampleListener mListener;
|
||||
|
||||
private float[] mSamples;
|
||||
|
||||
protected float mSum;
|
||||
|
||||
private int mSamplePointer = 0;
|
||||
private int mSize;
|
||||
|
||||
protected float mGain;
|
||||
|
||||
public Stage()
|
||||
{
|
||||
}
|
||||
|
||||
public Stage(int size)
|
||||
{
|
||||
mSize = size - 1;
|
||||
|
||||
mSamples = new float[mSize];
|
||||
|
||||
mGain = 1.0f / (float)size;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public void receive(float sample)
|
||||
{
|
||||
/* Subtract the oldest sample and add back in the newest */
|
||||
mSum = mSum - mSamples[mSamplePointer] + sample;
|
||||
|
||||
/* Overwrite the oldest sample with the newest */
|
||||
mSamples[mSamplePointer] = sample;
|
||||
|
||||
mSamplePointer++;
|
||||
|
||||
if(mSamplePointer >= mSize)
|
||||
{
|
||||
mSamplePointer = 0;
|
||||
}
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(mSum * mGain);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(RealSampleListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
||||
* a two-stage.
|
||||
*/
|
||||
public class TwoStage extends Stage
|
||||
{
|
||||
public TwoStage()
|
||||
{
|
||||
super();
|
||||
|
||||
mGain = 0.5f;
|
||||
}
|
||||
|
||||
public void receive(float sample)
|
||||
{
|
||||
float sum = mSum + sample;
|
||||
|
||||
mSum = sample;
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(sum * mGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction, applies cleanup filter, casts
|
||||
* double-valued samples into float-valued complex sample and sends the
|
||||
* result to the registered listener.
|
||||
*/
|
||||
public class Output implements RealSampleListener
|
||||
{
|
||||
/* Decimated output buffers will contain 1024 samples */
|
||||
private ReusableBufferAssembler mAssembler;
|
||||
private Listener<ReusableFloatBuffer> mReusableBufferListener;
|
||||
|
||||
private RealFIRFilter2 mCleanupFilter;
|
||||
private HalfBandFilter mHalfBandFilter = new HalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
||||
|
||||
public Output(int outputSampleRate, int passFrequency, int attenuation, WindowType windowType, int outputBufferSize)
|
||||
{
|
||||
mCleanupFilter = new RealFIRFilter2(FilterFactory.getCICCleanupFilter(outputSampleRate, passFrequency,
|
||||
attenuation, windowType), 0.4f);
|
||||
|
||||
mAssembler = new ReusableBufferAssembler(outputBufferSize);
|
||||
mAssembler.setListener(new Listener<ReusableFloatBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
|
||||
if(mReusableBufferListener != null)
|
||||
{
|
||||
ReusableFloatBuffer cleanupFiltered = mCleanupFilter.filter(reusableFloatBuffer);
|
||||
ReusableFloatBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
|
||||
mReusableBufferListener.receive(halfBandFiltered);
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableFloatBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mAssembler.dispose();
|
||||
mCleanupFilter.dispose();
|
||||
mHalfBandFilter.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver method for the output adapter to receive a filtered,
|
||||
* decimated sample, apply gain correction, apply cleanup filtering
|
||||
* and output the sample values as a complex sample.
|
||||
*/
|
||||
@Override
|
||||
public void receive(float sample)
|
||||
{
|
||||
mAssembler.receive(sample * 32.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive output samples
|
||||
*/
|
||||
public void setListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mReusableBufferListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving output samples
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mReusableBufferListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.correction;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||
|
||||
public class IQCorrectionFilter
|
||||
{
|
||||
private float mRatio = 0.00001f;
|
||||
private float mAverageInphase = 0.0f;
|
||||
private float mAverageQuadrature = 0.0f;
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("IQCorrectionFilter");
|
||||
|
||||
public IQCorrectionFilter(float ratio)
|
||||
{
|
||||
mRatio = ratio;
|
||||
}
|
||||
|
||||
public IQCorrectionFilter()
|
||||
{
|
||||
}
|
||||
|
||||
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
ReusableComplexBuffer filtered = mReusableComplexBufferQueue.getBuffer(samples.length);
|
||||
float[] filteredSamples = filtered.getSamples();
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
mAverageInphase = mAverageInphase + (mRatio * (samples[x] - mAverageInphase));
|
||||
filteredSamples[x] = samples[x] - mAverageInphase;
|
||||
|
||||
mAverageQuadrature = mAverageQuadrature + (mRatio * (samples[x + 1] - mAverageQuadrature));
|
||||
filteredSamples[x + 1] = samples[x + 1] - mAverageQuadrature;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BufferDCFilter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BufferDCFilter.class);
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Buffer DC Filter");
|
||||
private double mAccumulator;
|
||||
|
||||
/**
|
||||
* Calculates the DC offset present in the samples and subtracts the offset from all samples.
|
||||
*/
|
||||
public void filterReal(float[] samples)
|
||||
{
|
||||
float adjustment = getOffset(samples);
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
samples[x] = samples[x] - adjustment;
|
||||
}
|
||||
}
|
||||
|
||||
private float getOffset(float[] samples)
|
||||
{
|
||||
mAccumulator = 0.0;
|
||||
|
||||
for (float sample : samples) {
|
||||
mAccumulator += sample;
|
||||
}
|
||||
|
||||
return (float)(mAccumulator / (double)samples.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the DC offset present in the samples and subtracts the offset from all samples.
|
||||
*/
|
||||
public static void filterComplex(float[] samples)
|
||||
{
|
||||
double iAccumulator = 0.0;
|
||||
double qAccumulator = 0.0;
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
iAccumulator += samples[x];
|
||||
qAccumulator += samples[x + 1];
|
||||
}
|
||||
|
||||
double half = (double)samples.length / 2.0;
|
||||
|
||||
float iAdjustment = (float)(iAccumulator / half);
|
||||
float qAdjustment = (float)(qAccumulator / half);
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
samples[x] = samples[x] - iAdjustment;
|
||||
samples[x + 1] = samples[x + 1] - qAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
|
||||
{
|
||||
float[] unfilteredSamples = unfilteredBuffer.getSamples();
|
||||
|
||||
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
|
||||
float[] filteredSamples = filteredBuffer.getSamples();
|
||||
|
||||
float offset = getOffset(unfilteredSamples);
|
||||
|
||||
for(int x = 0; x < unfilteredSamples.length; x++)
|
||||
{
|
||||
filteredSamples[x] = unfilteredSamples[x] - offset;
|
||||
}
|
||||
|
||||
unfilteredBuffer.decrementUserCount();
|
||||
|
||||
return filteredBuffer;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/**
|
||||
* DC removal filter for removing any DC offset that may be present when a signal is not aligned within the
|
||||
* demodulated channel.
|
||||
*/
|
||||
public class DCRemovalFilter
|
||||
{
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("DC Removal Filter");
|
||||
protected float mAverage;
|
||||
protected float mRatio;
|
||||
|
||||
/**
|
||||
* Creates a DC removal filter.
|
||||
*
|
||||
* @param ratio
|
||||
*/
|
||||
public DCRemovalFilter(float ratio)
|
||||
{
|
||||
mRatio = ratio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the running average to zero.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mAverage = 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the sample and returns a filtered (ie DC-removed) sample.
|
||||
* @param sample to filter
|
||||
* @return filtered sample
|
||||
*/
|
||||
public float filter(float sample)
|
||||
{
|
||||
float filtered = sample - mAverage;
|
||||
mAverage += mRatio * filtered;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public float[] filter( float[] samples )
|
||||
{
|
||||
for( int x = 0; x < samples.length; x++ )
|
||||
{
|
||||
samples[ x ] = filter( samples[ x ] );
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the buffer samples and returns a new reusable buffer with the filtered samples.
|
||||
*
|
||||
* Note: this method automatically manages the user accounting between the buffer argument and the new returned
|
||||
* filtered sample buffer.
|
||||
*
|
||||
* @param unfilteredBuffer containing samples to filter
|
||||
* @return a new reusable buffer containing the filtered samples with the user count set to 1.
|
||||
*/
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
|
||||
{
|
||||
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
|
||||
|
||||
float[] unfilteredSamples = unfilteredBuffer.getSamples();
|
||||
float[] filteredSamples = filteredBuffer.getSamples();
|
||||
|
||||
for(int x = 0; x < unfilteredSamples.length; x++)
|
||||
{
|
||||
filteredSamples[x] = filter(unfilteredSamples[x]);
|
||||
}
|
||||
|
||||
unfilteredBuffer.decrementUserCount();
|
||||
|
||||
return filteredBuffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
|
||||
/**
|
||||
* Factory for constructing the optimal DC removal filter.
|
||||
*/
|
||||
public class DcRemovalFilterFactory
|
||||
{
|
||||
/**
|
||||
* Creates the optimal DC removal filter for this computer
|
||||
* @param gain to apply to DC removal
|
||||
* @return optimal implementation.
|
||||
*/
|
||||
public static IDcRemovalFilter getFilter(float gain)
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DC_REMOVAL_REAL);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_PREFERRED:
|
||||
return new VectorDcRemovalFilter(gain);
|
||||
case SCALAR:
|
||||
default:
|
||||
return new ScalarDcRemovalFilter(gain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
/**
|
||||
* Interface for a DC removal filter.
|
||||
*/
|
||||
public interface IDcRemovalFilter
|
||||
{
|
||||
float[] filter(float[] samples);
|
||||
}
|
||||
@@ -1,34 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
|
||||
public class IIRSinglePoleDCRemovalFilter
|
||||
{
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("IIR DC Filter");
|
||||
private float mAlpha;
|
||||
private float mPreviousInput = 0.0f;
|
||||
private float mPreviousOutput = 0.0f;
|
||||
private float mCurrentOutput = 0.0f;
|
||||
|
||||
private RealSampleListener mListener;
|
||||
|
||||
/**
|
||||
* IIR single-pole DC removal filter, as described by J M de Freitas in
|
||||
* 29Jan2007 paper at:
|
||||
@@ -64,42 +60,16 @@ public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
|
||||
* Buffer user accounting is handled by this method where the input buffer user count is decremented and the
|
||||
* returned output buffer has the user count incremented to one.
|
||||
*
|
||||
* @param inputBuffer to filter
|
||||
* @param samples to filter
|
||||
* @return filtered samples in a new (ie reused) output buffer
|
||||
*/
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer inputBuffer)
|
||||
public float[] filter(float[] samples)
|
||||
{
|
||||
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
|
||||
float[] inputSamples = inputBuffer.getSamples();
|
||||
float[] outputSamples = outputBuffer.getSamples();
|
||||
|
||||
for(int x = 0; x < inputSamples.length; x++)
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
outputSamples[x] = filter(inputSamples[x]);
|
||||
samples[x] = filter(samples[x]);
|
||||
}
|
||||
|
||||
inputBuffer.decrementUserCount();
|
||||
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the RealSampleListener interface for single sample streams.
|
||||
*/
|
||||
@Override
|
||||
public void receive(float sample)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(filter(sample));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the listener to receive filtered samples processed by the RealSampleListener interface
|
||||
*/
|
||||
public void setListener(RealSampleListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class ScalarDcRemovalFilter implements IDcRemovalFilter
|
||||
{
|
||||
private float mAverage;
|
||||
private float mGain;
|
||||
|
||||
public ScalarDcRemovalFilter(float gain)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] filter(float[] samples)
|
||||
{
|
||||
float accumulator = 0.0f;
|
||||
float average = mAverage;
|
||||
|
||||
for(float sample: samples)
|
||||
{
|
||||
accumulator += sample;
|
||||
}
|
||||
|
||||
float averageDCNow = (accumulator / samples.length) - average;
|
||||
|
||||
average += (mGain * averageDCNow);
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
samples[x] -= average;
|
||||
}
|
||||
|
||||
mAverage = average;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
System.out.println("Starting ...");
|
||||
Random random = new Random();
|
||||
ScalarDcRemovalFilter scalarFilter = new ScalarDcRemovalFilter(0.15f);
|
||||
VectorDcRemovalFilter vectorFilter = new VectorDcRemovalFilter(0.15f);
|
||||
|
||||
float dcOffset = 0.03f;
|
||||
float[] samples = new float[8192];
|
||||
|
||||
for(int y = 0; y < samples.length; y++)
|
||||
{
|
||||
samples[y] = (random.nextFloat() * 2.0f) - 1.0f + dcOffset;
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
for(int x = 0; x < 1_000_000; x++)
|
||||
{
|
||||
// scalarFilter.filter(samples);
|
||||
vectorFilter.filter(samples);
|
||||
}
|
||||
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println("Finished - Duration: " + duration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class VectorDcRemovalFilter implements IDcRemovalFilter
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
private float mAverageDc;
|
||||
private float mGain;
|
||||
|
||||
public VectorDcRemovalFilter(float gain)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] filter(float[] samples)
|
||||
{
|
||||
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
|
||||
for(int pointer = 0; pointer < samples.length; pointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
accumulator = accumulator.add(FloatVector.fromArray(VECTOR_SPECIES, samples, pointer));
|
||||
}
|
||||
|
||||
float sum = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
float average = mAverageDc;
|
||||
float averageDCNow = (sum / samples.length) - average;
|
||||
average += (mGain * averageDCNow);
|
||||
|
||||
for(int pointer = 0; pointer < samples.length; pointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
//Load into vector, subtract dc, and write back to sample array
|
||||
FloatVector.fromArray(VECTOR_SPECIES, samples, pointer).add(average).intoArray(samples, pointer);
|
||||
}
|
||||
|
||||
mAverageDc = average;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
System.out.println("Starting ...");
|
||||
Random random = new Random();
|
||||
VectorDcRemovalFilter filter = new VectorDcRemovalFilter(0.15f);
|
||||
|
||||
float dcOffset = 0.03f;
|
||||
|
||||
for(int x = 0; x < 10; x++)
|
||||
{
|
||||
float[] samples = new float[8192];
|
||||
|
||||
for(int y = 0; y < samples.length; y++)
|
||||
{
|
||||
samples[y] = (random.nextFloat() * 2.0f) - 1.0f + dcOffset;
|
||||
}
|
||||
|
||||
samples = filter.filter(samples);
|
||||
}
|
||||
|
||||
System.out.println("Finished!");
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 1024 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX1024Filter extends ComplexDecimateX512Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 1024 * 2;
|
||||
private static final int DECIMATE_BY_1024_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_1024_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX1024Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_1024_FILTER_LENGTH,
|
||||
DECIMATE_BY_1024_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 128 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX128Filter extends ComplexDecimateX64Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 128 * 2;
|
||||
private static final int DECIMATE_BY_128_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_128_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX128Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_128_FILTER_LENGTH,
|
||||
DECIMATE_BY_128_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 16 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX16Filter extends ComplexDecimateX8Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 16 * 2;
|
||||
private static final int DECIMATE_BY_16_FILTER_LENGTH = 15;
|
||||
private static final Window.WindowType DECIMATE_BY_16_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX16Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_16_FILTER_LENGTH,
|
||||
DECIMATE_BY_16_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 256 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX256Filter extends ComplexDecimateX128Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 256 * 2;
|
||||
private static final int DECIMATE_BY_256_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_256_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX256Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_256_FILTER_LENGTH,
|
||||
DECIMATE_BY_256_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public class ComplexDecimateX2Filter extends ComplexHalfBandDecimationFilter
|
||||
{
|
||||
private static final int DECIMATE_BY_2_FILTER_LENGTH = 63;
|
||||
private static final Window.WindowType DECIMATE_BY_2_WINDOW_TYPE = Window.WindowType.HAMMING;
|
||||
|
||||
/**
|
||||
* Creates a half band filter with inherent decimation by two.
|
||||
*/
|
||||
public ComplexDecimateX2Filter()
|
||||
{
|
||||
super(FilterFactory.getHalfBand(DECIMATE_BY_2_FILTER_LENGTH, DECIMATE_BY_2_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the samples length is an integer multiple of the specified validation multiple argument.
|
||||
* @param samples to validate
|
||||
* @param validationMultiple to validate against
|
||||
*/
|
||||
protected static void validate(float[] samples, int validationMultiple)
|
||||
{
|
||||
if(samples.length % validationMultiple != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample buffer length [" + samples.length +
|
||||
"] must be an integer multiple of " + validationMultiple);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 32 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX32Filter extends ComplexDecimateX16Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 32 * 2;
|
||||
private static final int DECIMATE_BY_32_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_32_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX32Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_32_FILTER_LENGTH,
|
||||
DECIMATE_BY_32_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 4 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX4Filter extends ComplexDecimateX2Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 4 * 2;
|
||||
private static final int DECIMATE_BY_4_FILTER_LENGTH = 23;
|
||||
private static final Window.WindowType DECIMATE_BY_4_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX4Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_4_FILTER_LENGTH,
|
||||
DECIMATE_BY_4_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 512 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX512Filter extends ComplexDecimateX256Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 512 * 2;
|
||||
private static final int DECIMATE_BY_512_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_512_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX512Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_512_FILTER_LENGTH,
|
||||
DECIMATE_BY_512_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 64 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX64Filter extends ComplexDecimateX32Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 64 * 2;
|
||||
private static final int DECIMATE_BY_64_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_64_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX64Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_64_FILTER_LENGTH,
|
||||
DECIMATE_BY_64_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandDecimationFilter;
|
||||
|
||||
/**
|
||||
* Decimate by 8 filter for complex valued sample buffers.
|
||||
*/
|
||||
public class ComplexDecimateX8Filter extends ComplexDecimateX4Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 8 * 2;
|
||||
private static final int DECIMATE_BY_8_FILTER_LENGTH = 15;
|
||||
private static final Window.WindowType DECIMATE_BY_8_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private ComplexHalfBandDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public ComplexDecimateX8Filter()
|
||||
{
|
||||
mFilter = new ComplexHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_8_FILTER_LENGTH,
|
||||
DECIMATE_BY_8_WINDOW_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] decimateComplex(float[] samples)
|
||||
{
|
||||
validate(samples, VALIDATION_LENGTH);
|
||||
|
||||
//Decimate by this filter, then by the parent decimation filter
|
||||
return super.decimateComplex(mFilter.decimateComplex(samples));
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,15 @@
|
||||
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating real and complex decimation filters.
|
||||
*/
|
||||
public class DecimationFilterFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DecimationFilterFactory.class);
|
||||
private static final int[] SUPPORTED_RATES = new int[]{0,2,4,8,16,32,64,128,256,512,1024};
|
||||
|
||||
/**
|
||||
@@ -66,41 +70,25 @@ public class DecimationFilterFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a complex-valued decimation filter for float array sample buffers providing greater than 100 dB of
|
||||
* attenuation for out of band signal aliases. Supports power of 2 decimation rates up to 1024.
|
||||
*
|
||||
* @param decimationRate requested @see SUPPORTED_RATES
|
||||
* @return constructed decimation filter
|
||||
* Finds the greatest factor of 2 decimation rate that is less than the requested rate.
|
||||
* @param requestedRate for decimation
|
||||
* @return factor of 2 decimation rate that is closest to the requested rate
|
||||
*/
|
||||
public static IComplexDecimationFilter getComplexDecimationFilter(int decimationRate)
|
||||
public static int getDecimationRate(int requestedRate)
|
||||
{
|
||||
switch(decimationRate)
|
||||
if(requestedRate < 2)
|
||||
{
|
||||
case 0:
|
||||
return new ComplexDecimateX0Filter();
|
||||
case 2:
|
||||
return new ComplexDecimateX2Filter();
|
||||
case 4:
|
||||
return new ComplexDecimateX4Filter();
|
||||
case 8:
|
||||
return new ComplexDecimateX8Filter();
|
||||
case 16:
|
||||
return new ComplexDecimateX16Filter();
|
||||
case 32:
|
||||
return new ComplexDecimateX32Filter();
|
||||
case 64:
|
||||
return new ComplexDecimateX64Filter();
|
||||
case 128:
|
||||
return new ComplexDecimateX128Filter();
|
||||
case 256:
|
||||
return new ComplexDecimateX256Filter();
|
||||
case 512:
|
||||
return new ComplexDecimateX512Filter();
|
||||
case 1024:
|
||||
return new ComplexDecimateX1024Filter();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported decimation rate: " + decimationRate +
|
||||
". Supported decimation rates are:" + SUPPORTED_RATES);
|
||||
throw new IllegalArgumentException("Requested decimation rate must be greater than 2");
|
||||
}
|
||||
|
||||
for(int x = SUPPORTED_RATES.length - 1; x >= 0; x--)
|
||||
{
|
||||
if(SUPPORTED_RATES[x] < requestedRate)
|
||||
{
|
||||
return SUPPORTED_RATES[x];
|
||||
}
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 1024 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX1024Filter extends RealDecimateX512Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 1024;
|
||||
private static final int DECIMATE_BY_1024_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_1024_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_1024_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX1024Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_1024_FILTER_LENGTH,
|
||||
DECIMATE_BY_1024_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_1024_FILTER_LENGTH,
|
||||
DECIMATE_BY_1024_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.gui.playlist.alias.ViewAliasIdentifierRequest;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 128 filter for real valued sample buffers.
|
||||
@@ -31,16 +29,16 @@ public class RealDecimateX128Filter extends RealDecimateX64Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 128;
|
||||
private static final int DECIMATE_BY_128_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_128_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_128_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX128Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_128_FILTER_LENGTH,
|
||||
DECIMATE_BY_128_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_128_FILTER_LENGTH,
|
||||
DECIMATE_BY_128_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 16 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX16Filter extends RealDecimateX8Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 16;
|
||||
private static final int DECIMATE_BY_16_FILTER_LENGTH = 15;
|
||||
private static final Window.WindowType DECIMATE_BY_16_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_16_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX16Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_16_FILTER_LENGTH,
|
||||
DECIMATE_BY_16_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_16_FILTER_LENGTH,
|
||||
DECIMATE_BY_16_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 256 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX256Filter extends RealDecimateX128Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 256;
|
||||
private static final int DECIMATE_BY_256_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_256_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_256_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX256Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_256_FILTER_LENGTH,
|
||||
DECIMATE_BY_256_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_256_FILTER_LENGTH,
|
||||
DECIMATE_BY_256_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2021 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
|
||||
@@ -20,23 +20,29 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public class RealDecimateX2Filter extends RealHalfBandDecimationFilter
|
||||
public class RealDecimateX2Filter implements IRealDecimationFilter
|
||||
{
|
||||
private static final int DECIMATE_BY_2_FILTER_LENGTH = 63;
|
||||
private static final Window.WindowType DECIMATE_BY_2_WINDOW_TYPE = Window.WindowType.HAMMING;
|
||||
private static final WindowType DECIMATE_BY_2_WINDOW_TYPE = WindowType.HAMMING;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Creates a half band filter with inherent decimation by two.
|
||||
*/
|
||||
public RealDecimateX2Filter()
|
||||
{
|
||||
super(FilterFactory.getHalfBand(DECIMATE_BY_2_FILTER_LENGTH, DECIMATE_BY_2_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_2_FILTER_LENGTH,
|
||||
DECIMATE_BY_2_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override public float[] decimateReal(float[] samples)
|
||||
{
|
||||
return mFilter.decimateReal(samples);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 32 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX32Filter extends RealDecimateX16Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 32;
|
||||
private static final int DECIMATE_BY_32_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_32_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_32_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX32Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_32_FILTER_LENGTH,
|
||||
DECIMATE_BY_32_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_32_FILTER_LENGTH,
|
||||
DECIMATE_BY_32_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 4 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX4Filter extends RealDecimateX2Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 4;
|
||||
private static final int DECIMATE_BY_4_FILTER_LENGTH = 23;
|
||||
private static final Window.WindowType DECIMATE_BY_4_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_4_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX4Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_4_FILTER_LENGTH,
|
||||
DECIMATE_BY_4_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_4_FILTER_LENGTH,
|
||||
DECIMATE_BY_4_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 512 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX512Filter extends RealDecimateX256Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 512;
|
||||
private static final int DECIMATE_BY_512_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_512_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_512_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX512Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_512_FILTER_LENGTH,
|
||||
DECIMATE_BY_512_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_512_FILTER_LENGTH,
|
||||
DECIMATE_BY_512_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.decimate;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.RealHalfBandDecimationFilter;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Decimate by 64 filter for real valued sample buffers.
|
||||
@@ -30,16 +29,16 @@ public class RealDecimateX64Filter extends RealDecimateX32Filter
|
||||
{
|
||||
private static final int VALIDATION_LENGTH = 64;
|
||||
private static final int DECIMATE_BY_64_FILTER_LENGTH = 11;
|
||||
private static final Window.WindowType DECIMATE_BY_64_WINDOW_TYPE = Window.WindowType.BLACKMAN;
|
||||
private RealHalfBandDecimationFilter mFilter;
|
||||
private static final WindowType DECIMATE_BY_64_WINDOW_TYPE = WindowType.BLACKMAN;
|
||||
private IRealDecimationFilter mFilter;
|
||||
|
||||
/**
|
||||
* Constructs the decimation filter.
|
||||
*/
|
||||
public RealDecimateX64Filter()
|
||||
{
|
||||
mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_64_FILTER_LENGTH,
|
||||
DECIMATE_BY_64_WINDOW_TYPE));
|
||||
mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_64_FILTER_LENGTH,
|
||||
DECIMATE_BY_64_WINDOW_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user