diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml index 2eb640e2..99c24efd 100644 --- a/.idea/dictionaries/denny.xml +++ b/.idea/dictionaries/denny.xml @@ -2,6 +2,7 @@ bahroo + basebanded broadcastify correlator deinterleave diff --git a/.idea/misc.xml b/.idea/misc.xml index 7976fb14..1db132d2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index fcd1e883..9aac098a 100644 --- a/build.gradle +++ b/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 - } - } -} diff --git a/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java b/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java index 31535c64..7a5391e9 100644 --- a/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ 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 diff --git a/src/main/java/io/github/dsheirer/audio/AudioModule.java b/src/main/java/io/github/dsheirer/audio/AudioModule.java index 13d3d2e3..2d984e68 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioModule.java +++ b/src/main/java/io/github/dsheirer/audio/AudioModule.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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 +public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IRealBufferListener, + Listener { 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 getReusableBufferListener() + public Listener getBufferListener() { //Redirect received reusable buffers to the receive(buffer) method return this; diff --git a/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java b/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java index 43e65f34..b7c54da0 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/AudioSegmentBroadcaster.java @@ -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; diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java index ab97502e..fb452d64 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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() diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java index 4ef5f396..6530410f 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java index fe496762..9e8dc9e6 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java index 996ef24b..7fe52d40 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java index ce80c707..541c46f3 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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 diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java index 9dbf88dc..474b6071 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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(); diff --git a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java index 3f17a518..30ca020e 100644 --- a/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java +++ b/src/main/java/io/github/dsheirer/audio/convert/thumbdv/ThumbDv.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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 mDecodeSpeechRequests = new LinkedTransferQueue<>(); private AudioProtocol mAudioProtocol; - private Listener mAudioBufferListener; - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ThumbDv"); + private Listener mAudioBufferListener; private boolean mStarted; - public ThumbDv(AudioProtocol audioProtocol, Listener listener) + public ThumbDv(AudioProtocol audioProtocol, Listener 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 listener = reusableAudioPacket -> { + final Listener listener = packet -> { mLog.info("Got an audio packet!"); - reusableAudioPacket.decrementUserCount(); }; try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener)) diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java index d3e1d269..ba9c3b2d 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioAdapter.java @@ -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 - ******************************************************************************/ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ 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; } diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java index f91ae5f7..aae21921 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java @@ -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 - ******************************************************************************/ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ 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] ); } } diff --git a/src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java new file mode 100644 index 00000000..f275a95f --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java @@ -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 + * **************************************************************************** + */ + +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 iterator() + { + return new ComplexSamplesIterator(); + } + + @Override + public Iterator iteratorInterleaved() + { + return new InterleavedComplexSamplesIterator(); + } + + private class ComplexSamplesIterator implements Iterator + { + 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 + { + 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); + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java new file mode 100644 index 00000000..489b258a --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/ByteNativeBufferFactory.java @@ -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 + * **************************************************************************** + */ + +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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java new file mode 100644 index 00000000..e1a9fbf1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java @@ -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 + * **************************************************************************** + */ + +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 iterator() + { + return new ComplexSamplesIterator(); + } + + @Override + public Iterator iteratorInterleaved() + { + return new InterleavedComplexSamplesIterator(); + } + + @Override + public int sampleCount() + { + return mInterleavedComplexSamples.length / 2; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + private class ComplexSamplesIterator implements Iterator + { + 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 + { + 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); + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/INativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/INativeBuffer.java new file mode 100644 index 00000000..6122ec44 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/INativeBuffer.java @@ -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 + * **************************************************************************** + */ + +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 iterator(); + + /** + * Provides an iterator that converts the raw samples to interleaved complex samples format. + */ + Iterator iteratorInterleaved(); + + /** + * Total number of complex samples for this buffer + */ + int sampleCount(); + + /** + * Timestamp for this buffer + * @return millis since epoch + */ + long getTimestamp(); +} diff --git a/src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java new file mode 100644 index 00000000..fb15bb54 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/INativeBufferFactory.java @@ -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 + * **************************************************************************** + */ + +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); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java b/src/main/java/io/github/dsheirer/buffer/INativeBufferProvider.java similarity index 61% rename from src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java rename to src/main/java/io/github/dsheirer/buffer/INativeBufferProvider.java index 1779ece9..82b3ad5a 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/IComplexDecimationFilter.java +++ b/src/main/java/io/github/dsheirer/buffer/INativeBufferProvider.java @@ -16,27 +16,27 @@ * along with this program. If not, see * **************************************************************************** */ +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 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 listener); + + /** + * Indicates if there are any buffer listeners registered + */ + boolean hasBufferListeners(); } diff --git a/src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java b/src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java new file mode 100644 index 00000000..0ad836a1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/NativeBufferPoisonPill.java @@ -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 + * **************************************************************************** + */ + +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 iterator() + { + return null; + } + + @Override + public Iterator iteratorInterleaved() + { + return null; + } + + @Override + public int sampleCount() + { + return 0; + } + + @Override + public long getTimestamp() + { + return 0; + } +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexDelayBuffer.java b/src/main/java/io/github/dsheirer/buffer/NativeSampleDelayBuffer.java similarity index 55% rename from src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexDelayBuffer.java rename to src/main/java/io/github/dsheirer/buffer/NativeSampleDelayBuffer.java index f196dcc6..c25792bf 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexDelayBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/NativeSampleDelayBuffer.java @@ -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 - * - ******************************************************************************/ -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 + * **************************************************************************** + */ +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 +public class NativeSampleDelayBuffer implements Listener { - private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexDelayBuffer.class); + private final static Logger mLog = LoggerFactory.getLogger(NativeSampleDelayBuffer.class); - private ReusableBufferBroadcaster mBroadcaster = new ReusableBufferBroadcaster<>(); - private LinkedTransferQueue mActionRequestQueue = new LinkedTransferQueue<>(); - private ActionRequest mActionRequest; - - private ReusableComplexBuffer[] mDelayBuffer; + private Broadcaster mBroadcaster = new Broadcaster<>(); + private LinkedTransferQueue mActionQueue = new LinkedTransferQueue<>(); + private INativeBuffer[] mDelayBuffer; private int mDelayBufferPointer = 0; private long mBufferDuration; @@ -57,9 +59,9 @@ public class ReusableComplexDelayBuffer implements Listener= mDelayBuffer.length) - { - mDelayBufferPointer = 0; - } + mDelayBufferPointer %= mDelayBuffer.length; } /** @@ -153,23 +139,18 @@ public class ReusableComplexDelayBuffer implements Listener= 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 listener, long timestamp) + public void addListener(Listener 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 listener) + public void removeListener(Listener 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 { private Action mAction; - private Listener mListener; + private Listener mListener; private long mTimestamp; /** @@ -219,9 +200,9 @@ public class ReusableComplexDelayBuffer implements Listener listener, long timestamp) + public ActionRequest(Listener listener, long timestamp) { - mAction = Action.ADD_USER; + mAction = Action.ADD_LISTENER; mListener = listener; mTimestamp = timestamp; } @@ -230,9 +211,9 @@ public class ReusableComplexDelayBuffer implements Listener listener) + public ActionRequest(Listener listener) { - mAction = Action.REMOVE_USER; + mAction = Action.REMOVE_LISTENER; mListener = listener; } @@ -252,7 +233,7 @@ public class ReusableComplexDelayBuffer implements Listener getListener() + public Listener getListener() { return mListener; } diff --git a/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java new file mode 100644 index 00000000..0d363213 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBuffer.java @@ -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 + * **************************************************************************** + */ + +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 iterator() + { + return new ComplexSamplesIterator(); + } + + @Override + public Iterator iteratorInterleaved() + { + return new InterleavedComplexSamplesIterator(); + } + + /** + * Iterator of complex samples over the native byte buffer array + */ + private class ComplexSamplesIterator implements Iterator + { + 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 + { + 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); + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java new file mode 100644 index 00000000..f7a63587 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/SignedByteNativeBufferFactory.java @@ -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 + * **************************************************************************** + */ + +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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java new file mode 100644 index 00000000..e41bec7b --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIterator.java @@ -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 + * **************************************************************************** + */ + +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 either ComplexSamples or InterleavedComplexSamples + */ +public abstract class AirspyBufferIterator implements Iterator +{ + 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; + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java new file mode 100644 index 00000000..805f8f43 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorScalar.java @@ -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 + * **************************************************************************** + */ + +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 +{ + /** + * 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java new file mode 100644 index 00000000..caa10e65 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector128Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java new file mode 100644 index 00000000..573fb699 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector256Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java new file mode 100644 index 00000000..bce6aa31 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector512Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java new file mode 100644 index 00000000..48e34c6a --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyBufferIteratorVector64Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java new file mode 100644 index 00000000..1e1e1e77 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorScalar.java @@ -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 + * **************************************************************************** + */ + +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 +{ + /** + * 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java new file mode 100644 index 00000000..9c244231 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector128Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java new file mode 100644 index 00000000..0cf7b8ac --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector256Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java new file mode 100644 index 00000000..1ee39ca1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector512Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java new file mode 100644 index 00000000..cdc714af --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyInterleavedBufferIteratorVector64Bits.java @@ -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 + * **************************************************************************** + */ + +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 +{ + private static final VectorSpecies 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); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java new file mode 100644 index 00000000..7459331b --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBuffer.java @@ -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 + * **************************************************************************** + */ + +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 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 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); + }; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java new file mode 100644 index 00000000..b8c349eb --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/AirspyNativeBufferFactory.java @@ -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 + * **************************************************************************** + */ + +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)]; + } + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java new file mode 100644 index 00000000..8164f37c --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/IAirspySampleConverter.java @@ -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 + * **************************************************************************** + */ + +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(); +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java new file mode 100644 index 00000000..0f1e0d60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarPackedSampleConverter.java @@ -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 + * **************************************************************************** + */ + +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"); + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java new file mode 100644 index 00000000..e2a11c4a --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/ScalarUnpackedSampleConverter.java @@ -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 + * **************************************************************************** + */ + +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; + } +} diff --git a/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java b/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java new file mode 100644 index 00000000..f94acf1e --- /dev/null +++ b/src/main/java/io/github/dsheirer/buffer/airspy/VectorUnpackedSampleConverter.java @@ -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 + * **************************************************************************** + */ + +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 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; + } +} diff --git a/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java b/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java index 10a5772c..e8b28120 100644 --- a/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java +++ b/src/main/java/io/github/dsheirer/channel/state/AbstractChannelState.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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 diff --git a/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java b/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java index 7bb8ac44..1f675ac4 100644 --- a/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java +++ b/src/main/java/io/github/dsheirer/controller/NamingThreadFactory.java @@ -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 + * **************************************************************************** + */ + 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 ) diff --git a/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java b/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java index f2b76925..0f11bf73 100644 --- a/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java +++ b/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ 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 +public class AFSK1200Decoder implements Listener { private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class); @@ -71,7 +69,7 @@ public class AFSK1200Decoder implements Listener 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 * @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 } - public class Decoder implements Listener + public class Decoder implements Listener { @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 { 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 * 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++) diff --git a/src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java b/src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java deleted file mode 100644 index 8c44b959..00000000 --- a/src/main/java/io/github/dsheirer/dsp/afsk/AFSKTest.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -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++; - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java index 7e540441..6c5ddf0f 100644 --- a/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/am/AMDemodulator.java @@ -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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ 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; } } diff --git a/src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java b/src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java new file mode 100644 index 00000000..a6c5bb25 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/am/IAmDemodulator.java @@ -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); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java index 94b29a63..21d31990 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java @@ -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++) { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/Filters.java b/src/main/java/io/github/dsheirer/dsp/filter/Filters.java deleted file mode 100644 index fe4cd96a..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/Filters.java +++ /dev/null @@ -1,1760 +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 Foundationf, either version 3 of the Licensef, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be usefulf, - * 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 notf, see - ******************************************************************************/ -package io.github.dsheirer.dsp.filter; - -public enum Filters -{ - /** - * Designed with t-filter: - * - * 48000 Fsf, 243 taps - * 0 - 200 Hz: gain=0f, -60 db attenuation - * 500 - 3500 Hz: gain=1f, 5 db ripple - * 3800 - 24000 Hz: gain=0f, -60 db attenuation - */ - FIR_BANDPASS_AUDIO_48KHZ( new float[] - { - 0.0011306590506720503f, - 0.0017695582496011488f, - 0.0029288606290379564f, - 0.004343691928519147f, - 0.005911947383484083f, - 0.007475952925152455f, - 0.008834973531280245f, - 0.009767639642827303f, - 0.010062783359359985f, - 0.009554323352836855f, - 0.008154040477083785f, - 0.005876856248820146f, - 0.002853293692694345f, - -0.0006754620931569513f, - -0.004380476393750235f, - -0.007883982012534343f, - -0.010810030687940836f, - -0.01283857015700313f, - -0.013754750291231674f, - -0.013485225355167158f, - -0.012113350628392553f, - -0.009870878494518202f, - -0.007105858136673649f, - -0.00423044627271302f, - -0.0016581171943216698f, - 0.00026139558898877413f, - 0.0012960093376189904f, - 0.001367153398534282f, - 0.0005589404072224782f, - -0.0008998262476139348f, - -0.002678680933513012f, - -0.004405628120562205f, - -0.005733894494038466f, - -0.006403098522792899f, - -0.0062840458452382f, - -0.005398740409728341f, - -0.003912212267810966f, - -0.0020979501642753354f, - -0.0002835213596901672f, - 0.0012129422773069776f, - 0.002143441512259471f, - 0.0023762621098547794f, - 0.0019187818063353935f, - 0.000912904484557303f, - -0.0003959227311914178f, - -0.0017094242365984f, - -0.0027368218536981716f, - -0.0032529110615098964f, - -0.003141865590241452f, - -0.0024186267939756956f, - -0.0012232782401918676f, - 0.00020964495297755235f, - 0.0016007876062091302f, - 0.0026840078948525774f, - 0.003261066093523398f, - 0.0032416075292524913f, - 0.0026604426307957627f, - 0.0016685677475029945f, - 0.0005002180750227251f, - -0.0005771658431263752f, - -0.001319472490248709f, - -0.0015570243575186143f, - -0.001229709577267077f, - -0.00039860814610671267f, - 0.000768061742498229f, - 0.0020316064815484648f, - 0.0031342758646814173f, - 0.00385441982862473f, - 0.004053691192616693f, - 0.003706243244244987f, - 0.0029036666957800066f, - 0.0018348347602710623f, - 0.0007447703354245662f, - -0.00011835819081745851f, - -0.000556901991979898f, - -0.0004664033507391891f, - 0.00014190043902796031f, - 0.001143626440427841f, - 0.0023282929683617563f, - 0.0034451626872284316f, - 0.004257806518147909f, - 0.004595703675845672f, - 0.00439150448243702f, - 0.0036959141111179176f, - 0.0026669294496819663f, - 0.00153556329292172f, - 0.0005555624481582027f, - -0.000052181722788229754f, - -0.00014822483506696354f, - 0.0002944283222452884f, - 0.0011838275788186557f, - 0.0023285887949390117f, - 0.003479479143540588f, - 0.004383876725492502f, - 0.004841511744105689f, - 0.004748927884234896f, - 0.0041230326070274305f, - 0.003098290748206268f, - 0.0018980051891599843f, - 0.0007858270723442799f, - 0.000007808226096047636f, - -0.00026221415675647226f, - 0.000038194038926661374f, - 0.0008450198034699045f, - 0.001980683488085903f, - 0.0031920946811098026f, - 0.004205885473834353f, - 0.004788786880449399f, - 0.004799721085068433f, - 0.004222198149342628f, - 0.0031694947167750084f, - 0.0018613072553364282f, - 0.0005764055253590191f, - -0.00040837636254631156f, - -0.0008797864800588528f, - -0.0007362748788250025f, - -0.000012335427115590454f, - 0.001127487196311246f, - 0.0024230493118988245f, - 0.0035750267398429276f, - 0.004310745684369589f, - 0.004445118905106224f, - 0.003923338081120285f, - 0.0028351651513023415f, - 0.0013974479410391886f, - -0.00009204763249292721f, - -0.0013196387156487223f, - -0.0020251151156756176f, - -0.0020605167563326203f, - -0.001424951849329983f, - -0.00026755861117644484f, - 0.0011426434337099476f, - 0.00247453898902279f, - 0.0034075979415458474f, - 0.0037034647048603147f, - 0.003260419292510863f, - 0.002138889696298502f, - 0.0005507292550901969f, - -0.0011837011788563476f, - -0.002705803369109791f, - -0.0036965273158039683f, - -0.003950008183602666f, - -0.0034202362142058866f, - -0.002235167107431455f, - -0.0006724174233870484f, - 0.0008995473194704798f, - 0.0021008342984556796f, - 0.0026253485580778205f, - 0.002310873178862991f, - 0.0011772529466448514f, - -0.0005730908358460114f, - -0.002594671172069867f, - -0.0044734850087020245f, - -0.005818409323341712f, - -0.006347432664306618f, - -0.005952913139489849f, - -0.004732154819606038f, - -0.0029702110984690744f, - -0.0010791651288411521f, - 0.00048707995367597095f, - 0.0013335442378021411f, - 0.0012177475494058294f, - 0.00010122252104995588f, - -0.0018287043085791768f, - -0.004200030123435349f, - -0.006529365603823069f, - -0.00832894491185446f, - -0.009214117900210558f, - -0.008992727392755416f, - -0.007713940553513187f, - -0.005665102752109283f, - -0.0033154814998344715f, - -0.001217542957348092f, - 0.00011370751541866088f, - 0.00031672583066655f, - -0.0007305199337543714f, - -0.002874449900177386f, - -0.005710232857479199f, - -0.008662772551611736f, - -0.01110852438766387f, - -0.01251284869187753f, - -0.012552561484583724f, - -0.011196629466395516f, - -0.008725049876131727f, - -0.005679263313240144f, - -0.0027527053850089833f, - -0.0006423534393274008f, - 0.00010613666306157791f, - -0.0007726767835356292f, - -0.0031920086018739423f, - -0.006715187307484388f, - -0.010635986592925665f, - -0.014122526644297223f, - -0.016396230121947006f, - -0.016907881072255753f, - -0.015472266669959437f, - -0.01232951904769663f, - -0.008115919582767236f, - -0.0037455665411447506f, - -0.00022384102223090357f, - 0.0015702170495901357f, - 0.0010871601110870473f, - -0.0017492942655052172f, - -0.006493113668388964f, - -0.012237738308695591f, - -0.017782884779579476f, - -0.021877188332801504f, - -0.02348982727475883f, - -0.022055126496704366f, - -0.017635878927117044f, - -0.010965170848193264f, - -0.0033482781673564166f, - 0.003565343786600591f, - 0.008103372983477226f, - 0.008926637620116478f, - 0.005350637848922081f, - -0.002434639433805644f, - -0.013297914897997802f, - -0.025277752058596798f, - -0.03586329253291671f, - -0.042403748965243175f, - -0.04257882777767779f, - -0.034847147989454305f, - -0.018789707031704826f, - 0.004719508132752991f, - 0.033554756785942955f, - 0.06459420543249415f, - 0.09415420005424562f, - 0.11853369816477846f, - 0.13457931451963423f, - 0.14017594101985517f, - 0.13457931451963423f, - 0.11853369816477848f, - 0.09415420005424562f, - 0.06459420543249415f, - 0.033554756785942955f, - 0.004719508132752991f, - -0.018789707031704822f, - -0.034847147989454305f, - -0.04257882777767779f, - -0.042403748965243175f, - -0.03586329253291671f, - -0.025277752058596798f, - -0.01329791489799781f, - -0.002434639433805644f, - 0.005350637848922081f, - 0.008926637620116478f, - 0.008103372983477226f, - 0.003565343786600591f, - -0.0033482781673564166f, - -0.01096517084819326f, - -0.017635878927117044f, - -0.022055126496704366f, - -0.02348982727475883f, - -0.021877188332801504f, - -0.017782884779579476f, - -0.012237738308695591f, - -0.006493113668388964f, - -0.0017492942655052172f, - 0.0010871601110870439f, - 0.0015702170495901396f, - -0.00022384102223090309f, - -0.0037455665411447506f, - -0.00811591958276724f, - -0.01232951904769663f, - -0.015472266669959437f, - -0.016907881072255753f, - -0.016396230121947006f, - -0.014122526644297223f, - -0.010635986592925665f, - -0.006715187307484388f, - -0.0031920086018739444f, - -0.0007726767835356292f, - 0.0001061366630615779f, - -0.0006423534393274008f, - -0.0027527053850089833f, - -0.005679263313240145f, - -0.00872504987613173f, - -0.011196629466395517f, - -0.012552561484583724f, - -0.01251284869187753f, - -0.011108524387663866f, - -0.008662772551611736f, - -0.005710232857479197f, - -0.002874449900177386f, - -0.0007305199337543743f, - 0.00031672583066655f, - 0.0001137075154186604f, - -0.001217542957348092f, - -0.0033154814998344715f, - -0.005665102752109283f, - -0.007713940553513191f, - -0.008992727392755416f, - -0.009214117900210558f, - -0.008328944911854458f, - -0.006529365603823069f, - -0.004200030123435349f, - -0.0018287043085791778f, - 0.00010122252104995396f, - 0.0012177475494058294f, - 0.0013335442378021411f, - 0.00048707995367597095f, - -0.0010791651288411521f, - -0.002970211098469072f, - -0.004732154819606038f, - -0.005952913139489849f, - -0.0063474326643066185f, - -0.005818409323341712f, - -0.004473485008702026f, - -0.002594671172069867f, - -0.0005730908358460094f, - 0.0011772529466448514f, - 0.002310873178862991f, - 0.002625348558077821f, - 0.0021008342984556796f, - 0.0008995473194704788f, - -0.0006724174233870484f, - -0.002235167107431454f, - -0.0034202362142058866f, - -0.003950008183602666f, - -0.0036965273158039683f, - -0.002705803369109791f, - -0.0011837011788563506f, - 0.0005507292550901969f, - 0.002138889696298502f, - 0.003260419292510862f, - 0.0037034647048603147f, - 0.0034075979415458474f, - 0.00247453898902279f, - 0.0011426434337099476f, - -0.00026755861117644543f, - -0.0014249518493299838f, - -0.0020605167563326203f, - -0.0020251151156756176f, - -0.001319638715648723f, - -0.00009204763249292625f, - 0.0013974479410391886f, - 0.0028351651513023415f, - 0.003923338081120284f, - 0.004445118905106224f, - 0.00431074568436959f, - 0.0035750267398429276f, - 0.0024230493118988228f, - 0.001127487196311246f, - -0.000012335427115590454f, - -0.0007362748788250025f, - -0.0008797864800588528f, - -0.0004083763625463096f, - 0.000576405525359021f, - 0.0018613072553364282f, - 0.0031694947167750084f, - 0.0042221981493426285f, - 0.004799721085068431f, - 0.004788786880449399f, - 0.004205885473834353f, - 0.0031920946811098017f, - 0.001980683488085902f, - 0.0008450198034699026f, - 0.00003819403892666041f, - -0.0002622141567564703f, - 0.000007808226096045708f, - 0.0007858270723442809f, - 0.001898005189159986f, - 0.003098290748206268f, - 0.004123032607027432f, - 0.004748927884234897f, - 0.004841511744105689f, - 0.004383876725492502f, - 0.003479479143540588f, - 0.0023285887949390117f, - 0.0011838275788186557f, - 0.00029442832224528743f, - -0.00014822483506696354f, - -0.000052181722788230235f, - 0.0005555624481582017f, - 0.0015355632929217181f, - 0.002666929449681966f, - 0.0036959141111179176f, - 0.004391504482437019f, - 0.004595703675845674f, - 0.004257806518147909f, - 0.0034451626872284316f, - 0.002328292968361755f, - 0.0011436264404278407f, - 0.0001419004390279603f, - -0.00046640335073919057f, - -0.000556901991979898f, - -0.00011835819081745802f, - 0.0007447703354245662f, - 0.0018348347602710623f, - 0.0029036666957800066f, - 0.0037062432442449863f, - 0.0040536911926166925f, - 0.003854419828624731f, - 0.0031342758646814165f, - 0.0020316064815484656f, - 0.00076806174249823f, - -0.00039860814610671267f, - -0.001229709577267077f, - -0.0015570243575186143f, - -0.001319472490248709f, - -0.0005771658431263752f, - 0.000500218075022725f, - 0.001668567747502994f, - 0.002660442630795762f, - 0.0032416075292524913f, - 0.0032610660935233986f, - 0.002684007894852579f, - 0.0016007876062091293f, - 0.00020964495297755235f, - -0.0012232782401918676f, - -0.002418626793975698f, - -0.003141865590241452f, - -0.003252911061509897f, - -0.002736821853698173f, - -0.0017094242365984003f, - -0.0003959227311914187f, - 0.0009129044845573019f, - 0.0019187818063353941f, - 0.0023762621098547807f, - 0.00214344151225947f, - 0.0012129422773069776f, - -0.0002835213596901672f, - -0.0020979501642753354f, - -0.003912212267810966f, - -0.005398740409728341f, - -0.0062840458452382f, - -0.006403098522792899f, - -0.005733894494038467f, - -0.004405628120562203f, - -0.002678680933513013f, - -0.0008998262476139358f, - 0.0005589404072224788f, - 0.001367153398534282f, - 0.0012960093376189895f, - 0.0002613955889887717f, - -0.0016581171943216698f, - -0.00423044627271302f, - -0.007105858136673649f, - -0.009870878494518204f, - -0.012113350628392553f, - -0.013485225355167158f, - -0.01375475029123167f, - -0.01283857015700313f, - -0.010810030687940836f, - -0.007883982012534343f, - -0.004380476393750235f, - -0.0006754620931569513f, - 0.0028532936926943457f, - 0.005876856248820146f, - 0.008154040477083787f, - 0.009554323352836855f, - 0.010062783359359985f, - 0.009767639642827303f, - 0.008834973531280245f, - 0.007475952925152455f, - 0.005911947383484083f, - 0.004343691928519149f, - 0.0029288606290379547f, - 0.00176955824960115f, - 0.0011306590506720503f - } ), - - FIRHP_LTR_DCBLOCK_750FS_50FC( new float[] - { - 0.0679294122464217f, - -0.07324095797199973f, - -0.03838823381974882f, - -0.013430897335300878f, - 0.006449207638138327f, - 0.022750490924149575f, - 0.034175688012529584f, - 0.037600969376502945f, - 0.030002787357015913f, - 0.009961076769098255f, - -0.02214359076495753f, - -0.062375656373163425f, - -0.10487871348895818f, - -0.14241968373731975f, - -0.16818470634383087f, - 0.8226518956829582f, - -0.16818470634383087f, - -0.14241968373731975f, - -0.10487871348895818f, - -0.062375656373163425f, - -0.02214359076495753f, - 0.009961076769098255f, - 0.030002787357015913f, - 0.037600969376502945f, - 0.034175688012529584f, - 0.022750490924149575f, - 0.006449207638138327f, - -0.013430897335300878f, - -0.03838823381974882f, - -0.07324095797199973f, - 0.0679294122464217f - }), - - FIRLP_LTR_1500FS( new float[] - { - -0.008272676875607624f, - -0.0024939020178146617f, - 0.010376092557732189f, - 0.011962525412519345f, - -0.003977368587769054f, - -0.013289773851060555f, - -0.0029497606301098858f, - 0.003941811083306652f, - -0.003154954069081114f, - 0.005485070321679545f, - 0.03171909301543344f, - 0.015387284536986508f, - -0.055467824110903244f, - -0.07246950346172203f, - 0.03466378905394898f, - 0.1322297692443333f, - 0.04683098737206109f, - -0.13469992806577527f, - -0.14450919452286415f, - 0.0578348928408703f, - 0.18926196545202326f, - 0.0578348928408703f, - -0.14450919452286415f, - -0.13469992806577527f, - 0.04683098737206109f, - 0.1322297692443333f, - 0.03466378905394898f, - -0.07246950346172203f, - -0.055467824110903244f, - 0.015387284536986508f, - 0.03171909301543344f, - 0.005485070321679545f, - -0.003154954069081114f, - 0.003941811083306652f, - -0.0029497606301098858f, - -0.013289773851060555f, - -0.003977368587769054f, - 0.011962525412519345f, - 0.010376092557732189f, - -0.0024939020178146617f, - -0.008272676875607624f - }), - - FIRBP_1200FSK_24000FS( new float[] - { - 0.004792788663425844f, - 0.004034377807422182f, - 0.004699808237099943f, - 0.00443210843891432f, - 0.0028638644082473f, - -0.00015292185838515035f, - -0.004394278846594682f, - -0.009212560275995686f, - -0.013596659741332163f, - -0.016383584186535002f, - -0.016573550574018152f, - -0.013657566836436204f, - -0.007851047182221552f, - -0.00014425394402752814f, - 0.007885453072038891f, - 0.014465082677564993f, - 0.01811703090261803f, - 0.01810997536219774f, - 0.014727244087571567f, - 0.009244939917342813f, - 0.0036035605020527947f, - -0.00014781830127321208f, - -0.0004913597198315713f, - 0.0030136991548897693f, - 0.009453462203915387f, - 0.01666055505335309f, - 0.02170180561798826f, - 0.021656701887523472f, - 0.014498683513932197f, - -0.0001565221289617459f, - -0.02065343799220559f, - -0.04339467795574746f, - -0.06347784859804807f, - -0.07576298936541592f, - -0.07612281593093975f, - -0.06256712023008996f, - -0.03596397841747506f, - -0.00012170810308311971f, - 0.03885147059625151f, - 0.07377979316631962f, - 0.09792980519752477f, - 0.10655596332800969f, - 0.09792980519752477f, - 0.07377979316631962f, - 0.03885147059625151f, - -0.00012170810308311971f, - -0.03596397841747506f, - -0.06256712023008996f, - -0.07612281593093975f, - -0.07576298936541592f, - -0.06347784859804807f, - -0.04339467795574746f, - -0.02065343799220559f, - -0.0001565221289617459f, - 0.014498683513932197f, - 0.021656701887523472f, - 0.02170180561798826f, - 0.01666055505335309f, - 0.009453462203915387f, - 0.0030136991548897693f, - -0.0004913597198315713f, - -0.00014781830127321208f, - 0.0036035605020527947f, - 0.009244939917342813f, - 0.014727244087571567f, - 0.01810997536219774f, - 0.01811703090261803f, - 0.014465082677564993f, - 0.007885453072038891f, - -0.00014425394402752814f, - -0.007851047182221552f, - -0.013657566836436204f, - -0.016573550574018152f, - -0.016383584186535002f, - -0.013596659741332163f, - -0.009212560275995686f, - -0.004394278846594682f, - -0.00015292185838515035f, - 0.0028638644082473f, - 0.00443210843891432f, - 0.004699808237099943f, - 0.004034377807422182f, - 0.004792788663425844f - }), - - FIRLP_3K( new float[] - { - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 48000 Hz - - * 0 Hz - 3000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 4.1593978370041 dB - - * 4000 Hz - 24000 Hz - gain = 0 - desired attenuation = -80 dB - actual attenuation = -80.03322246183818 dB - - */ - - -0.00011443124614437417f, - -0.00017293152659867192f, - -0.0002537395679787903f, - -0.0002960101735353313f, - -0.00023955416024142116f, - -0.000005428557963321982f, - 0.0004964100334261843f, - 0.0013538842757409063f, - 0.002634918910611058f, - 0.00436693575408337f, - 0.0065175161697321555f, - 0.00897952475800971f, - 0.011566379161822652f, - 0.014018876408451886f, - 0.016028053953955006f, - 0.017270887068042688f, - 0.01745751423179922f, - 0.0163833187291431f, - 0.013977100662194252f, - 0.010337169842377791f, - 0.005746282323474722f, - 0.0006589019184637925f, - -0.004341600521758248f, - -0.008613131831428159f, - -0.011545791615276019f, - -0.012660543617859996f, - -0.011700549440186059f, - -0.008698639621450092f, - -0.004006684845818583f, - 0.00172317360274727f, - 0.007608807120062409f, - 0.012654244324839246f, - 0.015898391911284164f, - 0.016575938784747404f, - 0.014265883702010186f, - 0.009002241493722784f, - 0.0013255862247370318f, - -0.007739007276591945f, - -0.0167810834076902f, - -0.024172675639705345f, - -0.02828537119270343f, - -0.027727506101182933f, - -0.02156733481426441f, - -0.009507700677949931f, - 0.008016566866990643f, - 0.02983764491633055f, - 0.05415546347475946f, - 0.0787307236559999f, - 0.10114644750135711f, - 0.11910360136213424f, - 0.13071022495045617f, - 0.13472344727490876f, - 0.13071022495045617f, - 0.11910360136213424f, - 0.10114644750135711f, - 0.0787307236559999f, - 0.05415546347475946f, - 0.02983764491633055f, - 0.008016566866990643f, - -0.009507700677949931f, - -0.02156733481426441f, - -0.027727506101182933f, - -0.02828537119270343f, - -0.024172675639705345f, - -0.0167810834076902f, - -0.007739007276591945f, - 0.0013255862247370318f, - 0.009002241493722784f, - 0.014265883702010186f, - 0.016575938784747404f, - 0.015898391911284164f, - 0.012654244324839246f, - 0.007608807120062409f, - 0.00172317360274727f, - -0.004006684845818583f, - -0.008698639621450092f, - -0.011700549440186059f, - -0.012660543617859996f, - -0.011545791615276019f, - -0.008613131831428159f, - -0.004341600521758248f, - 0.0006589019184637925f, - 0.005746282323474722f, - 0.010337169842377791f, - 0.013977100662194252f, - 0.0163833187291431f, - 0.01745751423179922f, - 0.017270887068042688f, - 0.016028053953955006f, - 0.014018876408451886f, - 0.011566379161822652f, - 0.00897952475800971f, - 0.0065175161697321555f, - 0.00436693575408337f, - 0.002634918910611058f, - 0.0013538842757409063f, - 0.0004964100334261843f, - -0.000005428557963321982f, - -0.00023955416024142116f, - -0.0002960101735353313f, - -0.0002537395679787903f, - -0.00017293152659867192f, - -0.00011443124614437417f - } ), - - FIRLP_8K( new float[] { - - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 48000 Hz - - * 0 Hz - 4000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 4.166465745565044 dB - - * 4600 Hz - 24000 Hz - gain = 0 - desired attenuation = -60 dB - actual attenuation = -60.01902518862098 dB - - */ - - -0.0005876043141107231f, - 0.0000395798795893033f, - 0.0007936108691418746f, - 0.002271220583758663f, - 0.0045690708238380244f, - 0.007632833240537176f, - 0.011210445195711924f, - 0.014848077882921171f, - 0.01794155760934873f, - 0.019840991376714804f, - 0.01999155424676768f, - 0.018081523929298803f, - 0.01415831726867514f, - 0.008676730505642374f, - 0.0024562153938831365f, - -0.0034571527303437705f, - -0.008001879410852286f, - -0.010340843984508226f, - -0.010072395969237698f, - -0.007354895192546896f, - -0.0029039203557233197f, - 0.0021492484218134564f, - 0.006514352308475102f, - 0.009056570176096615f, - 0.009090754245710363f, - 0.006576743943510026f, - 0.002156484338606342f, - -0.0029881335341335434f, - -0.007431040256860951f, - -0.009881347307264159f, - -0.009547761214420764f, - -0.006387716720906473f, - -0.00116384277476187f, - 0.004722257199180802f, - 0.0095865822102477f, - 0.011924484237795421f, - 0.010856600363787754f, - 0.006429626923814576f, - -0.00032219256325637487f, - -0.007591488679896054f, - -0.01325346967699737f, - -0.015451428687349951f, - -0.013159262174429455f, - -0.006567549275313882f, - 0.0028476895325932673f, - 0.012598008810195174f, - 0.019781804328972176f, - 0.02186025227953089f, - 0.017422058628364847f, - 0.006738234527549823f, - -0.008066547704296321f, - -0.023306710233840622f, - -0.0344567035523893f, - -0.037158269971142174f, - -0.028289474602001076f, - -0.006838858354697991f, - 0.02563946671507246f, - 0.06512400470969f, - 0.10578445793560229f, - 0.141076536562424f, - 0.16504202130157974f, - 0.17352664849913732f, - 0.16504202130157974f, - 0.141076536562424f, - 0.10578445793560229f, - 0.06512400470969f, - 0.02563946671507246f, - -0.006838858354697991f, - -0.028289474602001076f, - -0.037158269971142174f, - -0.0344567035523893f, - -0.023306710233840622f, - -0.008066547704296321f, - 0.006738234527549823f, - 0.017422058628364847f, - 0.02186025227953089f, - 0.019781804328972176f, - 0.012598008810195174f, - 0.0028476895325932673f, - -0.006567549275313882f, - -0.013159262174429455f, - -0.015451428687349951f, - -0.01325346967699737f, - -0.007591488679896054f, - -0.00032219256325637487f, - 0.006429626923814576f, - 0.010856600363787754f, - 0.011924484237795421f, - 0.0095865822102477f, - 0.004722257199180802f, - -0.00116384277476187f, - -0.006387716720906473f, - -0.009547761214420764f, - -0.009881347307264159f, - -0.007431040256860951f, - -0.0029881335341335434f, - 0.002156484338606342f, - 0.006576743943510026f, - 0.009090754245710363f, - 0.009056570176096615f, - 0.006514352308475102f, - 0.0021492484218134564f, - -0.0029039203557233197f, - -0.007354895192546896f, - -0.010072395969237698f, - -0.010340843984508226f, - -0.008001879410852286f, - -0.0034571527303437705f, - 0.0024562153938831365f, - 0.008676730505642374f, - 0.01415831726867514f, - 0.018081523929298803f, - 0.01999155424676768f, - 0.019840991376714804f, - 0.01794155760934873f, - 0.014848077882921171f, - 0.011210445195711924f, - 0.007632833240537176f, - 0.0045690708238380244f, - 0.002271220583758663f, - 0.0007936108691418746f, - 0.0000395798795893033f, - -0.0005876043141107231f - } ), - - FIRLP_15K( new float[] { - /* - FIR filter designed with http://t-filter.appspot.com - sampling frequency: 48000 Hz - * 0 Hz - 7000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 3.6923464109664326 dB - * 9000 Hz - 24000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -41.03342332843521 dB - */ - -0.014082901225560598f, - -0.02333969799770533f, - -0.022033345340200808f, - -0.0004492742003176776f, - 0.034217272170844526f, - 0.05847499164066433f, - 0.04845184454729058f, - 0.0029885635174003813f, - -0.045972124010673326f, - -0.04957432533335844f, - 0.02225593631403953f, - 0.15189293741405197f, - 0.27718300156351594f, - 0.32893125787628796f, - 0.27718300156351594f, - 0.15189293741405197f, - 0.02225593631403953f, - -0.04957432533335844f, - -0.045972124010673326f, - 0.0029885635174003813f, - 0.04845184454729058f, - 0.05847499164066433f, - 0.034217272170844526f, - -0.0004492742003176776f, - -0.022033345340200808f, - -0.02333969799770533f, - -0.014082901225560598f - } ), - - //FS=192000 FC=40000 WIN: Kaiser-Bessel - FIR_HALF_BAND_31T_ONE_EIGHTH_FCO( new float[] { - -0.000045886616046836f, 0f, - 0.000591369980811269f, 0f, - -0.002865894676690263f, 0f, - 0.008258847333251895f, 0f, - -0.019555838964709603f, 0f, - 0.041885910942306370f, 0f, - -0.091494773870957058f, 0f, - 0.313146427083389300f, 0.5f, //.499981677577289670f, - 0.313146427083389300f, 0f, - -0.091494773870957058f, 0f, - 0.041885910942306370f, 0f, - -0.019555838964709603f, 0f, - 0.008258847333251895f, 0f, - -0.002865894676690263f, 0f, - 0.000591369980811269f, 0f, - -0.000045886616046836f - } ), - FIRHP_31T_48KFS_300FCO( new float[] { 0.079454311268354116f, - -0.431252134555296540f, -0.080866558157993096f, -0.018201087229858796f, - -0.006959451230405745f, -0.005082182047181806f, -0.004827982359353442f, - -0.004678622485731351f, -0.004518783884113521f, -0.004590986714193629f, - -0.004788367734908081f, -0.004864687584309190f, -0.004847881778381928f, - -0.004819115432733451f, -0.004899065399109821f, 0.995074461482826860f, - -0.004899065399109821f, -0.004819115432733451f, -0.004847881778381928f, - -0.004864687584309190f, -0.004788367734908081f, -0.004590986714193629f, - -0.004518783884113521f, -0.004678622485731351f, -0.004827982359353442f, - -0.005082182047181806f, -0.006959451230405745f, -0.018201087229858796f, - -0.080866558157993096f, -0.431252134555296540f, 0.079454311268354116f } ), - - FIRHP_31T_12KFS_400FCO( new float[] { 0.053389156955298771f, - 0.182002884434506210f, -0.086127791245869528f, -0.016516534724731153f, - -0.036968390772931715f, -0.035475484228869217f, -0.040734954194563675f, - -0.044789081155425826f, -0.049513180269900807f, -0.054086500917566893f, - -0.058563271660686450f, -0.062449865988963169f, -0.065441928590436960f, - -0.068170371167388846f, -0.069540820637351994f, 0.930065488549365990f, - -0.069540820637351994f, -0.068170371167388846f, -0.065441928590436960f, - -0.062449865988963169f, -0.058563271660686450f, -0.054086500917566893f, - -0.049513180269900807f, -0.044789081155425826f, -0.040734954194563675f, - -0.035475484228869217f, -0.036968390772931715f, -0.016516534724731153f, - -0.086127791245869528f, 0.182002884434506210f, 0.053389156955298771f } ), - - /** - * FIR Low Pass Filter - * Sample Rate: 48000 - * Taps: 55 - * Cutoff Freq: 3000 Hz - * Designer: http://t-filter.appspot.com/fir/index.html - */ - - FIRLP_55TAP_48000FS_3000FC( 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 } ), - - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 24000 Hz - - * 0 Hz - 1400 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 4.175011388566792 dB - - * 1500 Hz - 12000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -40.00189415137373 dB - - */ - FIRLP_24000FS_TO_3000FS( new float[] { - - /* - - FIR filter designed with - http://t-filter.appspot.com - - sampling frequency: 48000 Hz - - * 0 Hz - 3000 Hz - gain = 1 - desired ripple = 5 dB - actual ripple = 3.939100294265316 dB - - * 3500 Hz - 24000 Hz - gain = 0 - desired attenuation = -40 dB - actual attenuation = -40.489198844529355 dB - - */ - - .2f,.2f,.2f,.2f,.2f,.2f,.2f - }), - - FIRLP_3000FS_TO_150FC( new float[] { - /* FIR filter designed with http://t-filter.appspot.com sampling frequency: 3000 Hz - * 0 Hz - 155 Hz gain = 1 desired ripple = 5 dB actual ripple = 4.1713329390800755 dB - * 160 Hz - 1500 Hz gain = 0 desired attenuation = -40 dB actual attenuation = -40.00926353319547 dB - */ - -0.006122920352830965f, - -0.0023884346335371165f, - -0.002599310268302821f, - -0.002619226598223891f, - -0.0024058915455907196f, - -0.0019288301578398123f, - -0.0011732914244696805f, - -0.00014348647168516363f, - 0.0011360279713313209f, - 0.002620018618160862f, - 0.004244279668461443f, - 0.005929041211240287f, - 0.007583700132903604f, - 0.009112816853143859f, - 0.010422968037142797f, - 0.011429759533610568f, - 0.012064461696610605f, - 0.01227985703081096f, - 0.012054670302231996f, - 0.011395973228693276f, - 0.010339492321850014f, - 0.008947826461137941f, - 0.007306289027941143f, - 0.005516680433668245f, - 0.003689888284206567f, - 0.0019376716631701368f, - 0.00036406537300421297f, - -0.0009423273945619157f, - -0.0019147615431778597f, - -0.0025137861299055595f, - -0.00272885000805846f, - -0.002579780077995427f, - -0.00211370881796881f, - -0.001401086778987431f, - -0.0005294931107453994f, - 0.00040506185948651675f, - 0.0013056441129537509f, - 0.0020838354221374305f, - 0.0026667562654551246f, - 0.003003052919640715f, - 0.003066845756573675f, - 0.002858826770125135f, - 0.002405247908616018f, - 0.0017550758096436146f, - 0.0009750187684900944f, - 0.0001428944628485573f, - -0.0006600057708364918f, - -0.0013572018806166464f, - -0.0018849613428995964f, - -0.0021983440926621103f, - -0.002274533536918245f, - -0.0021136819231921474f, - -0.0017380160407736585f, - -0.0011899674040168643f, - -0.0005293364966829129f, - 0.00017203581592664565f, - 0.0008397052861045773f, - 0.0014076463254305174f, - 0.0018238726340835366f, - 0.0020485384828633777f, - 0.002054368304440125f, - 0.00184518937028339f, - 0.001463759939291864f, - 0.0009254779772329933f, - 0.00030908834306052194f, - -0.00033072041687547837f, - -0.0009242244673079876f, - -0.001411151838185113f, - -0.0017423327816480623f, - -0.001885184212057672f, - -0.001827180245955061f, - -0.0015767572410517868f, - -0.0011623018660948076f, - -0.0006290757127071664f, - -0.000034277486241399186f, - 0.0005590116846630576f, - 0.0010883547994854647f, - 0.0014984460140958136f, - 0.0017469004431845741f, - 0.0018085733127357138f, - 0.00167799070404902f, - 0.0013699023886079994f, - 0.0009178041117923993f, - 0.00037029761935479554f, - -0.00021414834948363428f, - -0.0007733385753572961f, - -0.0012479991812573157f, - -0.0015880719715790936f, - -0.0017577706864892022f, - -0.0017393843977854672f, - -0.0015352614436320124f, - -0.0011668823340746201f, - -0.0006738084260047715f, - -0.00010821707107113083f, - 0.00046927876230741587f, - 0.0009969768021073388f, - 0.001418713685742809f, - 0.001689241246422699f, - 0.0017799814611489096f, - 0.0016808579160732414f, - 0.001401938565514821f, - 0.0009725070918705811f, - 0.00043789676780500817f, - -0.00014493157532811753f, - -0.0007134285789517371f, - -0.0012066408059786802f, - -0.0015718235937901522f, - -0.0017696752225057275f, - -0.0017782857455891608f, - -0.0015955792180418218f, - -0.001239958862296361f, - -0.00074891505028209f, - -0.0001750373426426684f, - 0.0004203972508663324f, - 0.0009743368473658559f, - 0.001427583798294352f, - 0.0017300695180374488f, - 0.0018474313131084748f, - 0.0017665803736364497f, - 0.0014959523321337515f, - 0.0010620969968500548f, - 0.0005087929550228872f, - -0.00010287888971484906f, - -0.0007071978327354896f, - -0.0012439856702169039f, - -0.0016485023632017621f, - -0.0018816779265696613f, - -0.0019137822019732152f, - -0.0017410363220249359f, - -0.0013797190792108685f, - -0.0008669516848493666f, - -0.0002564820252824784f, - 0.00038702570052167175f, - 0.0009946245737452108f, - 0.0015005576753828536f, - 0.0018494543772917837f, - 0.002002271293795752f, - 0.0019405399480025168f, - 0.001668583653638679f, - 0.0012133648938024923f, - 0.0006218226272857692f, - -0.00004396586555373278f, - -0.0007131888239163173f, - -0.0013137753201852866f, - -0.0017802301804471922f, - -0.002060772342951284f, - -0.0021230164816546412f, - -0.0019576726289922586f, - -0.001579698202436061f, - -0.0010269473735399154f, - -0.0003566571442405154f, - 0.00036046532149158213f, - 0.001047910209033917f, - 0.0016308959748307101f, - 0.0020457879850275806f, - 0.0022452826272775055f, - 0.0022053677261764473f, - 0.0019268362424314605f, - 0.001436104198970914f, - 0.000783301048300022f, - 0.000036170113001030666f, - -0.0007258491765731604f, - -0.0014208542690678963f, - -0.001973351045206107f, - -0.0023218012876373207f, - -0.002425430974535461f, - -0.0022692307984843695f, - -0.0018659822196923194f, - -0.0012553733554405261f, - -0.0005001275988053344f, - 0.0003207533398910953f, - 0.0011202470135660719f, - 0.0018119245687932325f, - 0.0023191095738080646f, - 0.0025840361579153536f, - 0.0025746366972740037f, - 0.0022874947808971076f, - 0.0017482590981993708f, - 0.001010469529718797f, - 0.00015080284754578053f, - -0.0007401321025391216f, - -0.0015677281601321103f, - -0.002241555313336647f, - -0.002685049904781321f, - -0.002847415468166842f, - -0.002706332216634437f, - -0.0022693572465456347f, - -0.0015807551810501244f, - -0.0007073681818317356f, - 0.00025858155245919815f, - 0.0012156374711696657f, - 0.0020601250277244316f, - 0.002698864465909288f, - 0.003058968365749582f, - 0.003095930462187589f, - 0.002799180323785052f, - 0.0021938662312239255f, - 0.0013387749941891575f, - 0.00032073673872905727f, - -0.0007540149497279023f, - -0.0017707823148707424f, - -0.0026186149816637165f, - -0.0032023981947833136f, - -0.0034533803843191375f, - -0.0033370666249195324f, - -0.002857634299302074f, - -0.0020583398437455315f, - -0.0010178803764328268f, - 0.00015714303240366978f, - 0.00134312158248896f, - 0.0024123674642028033f, - 0.0032466971416879246f, - 0.003750429521389635f, - 0.0038610298842211605f, - 0.0035563826341158213f, - 0.0028590450497876694f, - 0.0018334905123878731f, - 0.0005825119501486656f, - -0.0007653431253644171f, - -0.002066921044075508f, - -0.00318050748566926f, - -0.003981099599522876f, - -0.004372950526822947f, - -0.004302245531766623f, - -0.0037634623427922525f, - -0.0028010281264617036f, - -0.0015063684237781073f, - -0.000009312999857163947f, - 0.0015348340498211675f, - 0.002960705851903475f, - 0.00411025264482529f, - 0.004850497409323919f, - 0.00508852374186094f, - 0.004782051229146126f, - 0.003945677268175109f, - 0.002651861057270526f, - 0.001024987337059874f, - -0.000770898857558077f, - -0.0025480549808695363f, - -0.0041134371938038455f, - -0.005289473467472686f, - -0.0059345294154884536f, - -0.005958790652492603f, - -0.0053348632172493865f, - -0.004104133685973202f, - -0.0023757889373754375f, - -0.0003154863263286177f, - 0.0018679968592255536f, - 0.003942664171895544f, - 0.005680643711722206f, - 0.006877057686831076f, - 0.007378348952494577f, - 0.007096715159487296f, - 0.0060250187066667035f, - 0.004239992353086172f, - 0.0018984740602875209f, - -0.0007749973681354589f, - -0.0035085807133030038f, - -0.0060094188052075385f, - -0.007993440148818328f, - -0.009215708970605762f, - -0.00949819102645701f, - -0.008752165260963618f, - -0.006992771039067642f, - -0.004343750499151247f, - -0.0010313778997952953f, - 0.002632475569255834f, - 0.006276994042887084f, - 0.009506525921431109f, - 0.011939275012512792f, - 0.013247115357734634f, - 0.013192709905840089f, - 0.011660378948982f, - 0.008677354388180201f, - 0.0044226259943339384f, - -0.0007776763994276021f, - -0.006468598011493521f, - -0.01209866234178173f, - -0.01705984630504337f, - -0.020740073978089375f, - -0.022572656974485978f, - -0.022091348052909576f, - -0.018974044064189968f, - -0.01307870343426492f, - -0.004467461602914141f, - 0.006588374127832776f, - 0.019617756508396658f, - 0.03397630130130575f, - 0.04888871785298119f, - 0.06350208150118342f, - 0.07694602080488765f, - 0.08839667839951919f, - 0.09713857345310689f, - 0.10261800054825827f, - 0.10448449122868045f, - 0.10261800054825827f, - 0.09713857345310689f, - 0.08839667839951919f, - 0.07694602080488765f, - 0.06350208150118342f, - 0.04888871785298119f, - 0.03397630130130575f, - 0.019617756508396658f, - 0.006588374127832776f, - -0.004467461602914141f, - -0.01307870343426492f, - -0.018974044064189968f, - -0.022091348052909576f, - -0.022572656974485978f, - -0.020740073978089375f, - -0.01705984630504337f, - -0.01209866234178173f, - -0.006468598011493521f, - -0.0007776763994276021f, - 0.0044226259943339384f, - 0.008677354388180201f, - 0.011660378948982f, - 0.013192709905840089f, - 0.013247115357734634f, - 0.011939275012512792f, - 0.009506525921431109f, - 0.006276994042887084f, - 0.002632475569255834f, - -0.0010313778997952953f, - -0.004343750499151247f, - -0.006992771039067642f, - -0.008752165260963618f, - -0.00949819102645701f, - -0.009215708970605762f, - -0.007993440148818328f, - -0.0060094188052075385f, - -0.0035085807133030038f, - -0.0007749973681354589f, - 0.0018984740602875209f, - 0.004239992353086172f, - 0.0060250187066667035f, - 0.007096715159487296f, - 0.007378348952494577f, - 0.006877057686831076f, - 0.005680643711722206f, - 0.003942664171895544f, - 0.0018679968592255536f, - -0.0003154863263286177f, - -0.0023757889373754375f, - -0.004104133685973202f, - -0.0053348632172493865f, - -0.005958790652492603f, - -0.0059345294154884536f, - -0.005289473467472686f, - -0.0041134371938038455f, - -0.0025480549808695363f, - -0.000770898857558077f, - 0.001024987337059874f, - 0.002651861057270526f, - 0.003945677268175109f, - 0.004782051229146126f, - 0.00508852374186094f, - 0.004850497409323919f, - 0.00411025264482529f, - 0.002960705851903475f, - 0.0015348340498211675f, - -0.000009312999857163947f, - -0.0015063684237781073f, - -0.0028010281264617036f, - -0.0037634623427922525f, - -0.004302245531766623f, - -0.004372950526822947f, - -0.003981099599522876f, - -0.00318050748566926f, - -0.002066921044075508f, - -0.0007653431253644171f, - 0.0005825119501486656f, - 0.0018334905123878731f, - 0.0028590450497876694f, - 0.0035563826341158213f, - 0.0038610298842211605f, - 0.003750429521389635f, - 0.0032466971416879246f, - 0.0024123674642028033f, - 0.00134312158248896f, - 0.00015714303240366978f, - -0.0010178803764328268f, - -0.0020583398437455315f, - -0.002857634299302074f, - -0.0033370666249195324f, - -0.0034533803843191375f, - -0.0032023981947833136f, - -0.0026186149816637165f, - -0.0017707823148707424f, - -0.0007540149497279023f, - 0.00032073673872905727f, - 0.0013387749941891575f, - 0.0021938662312239255f, - 0.002799180323785052f, - 0.003095930462187589f, - 0.003058968365749582f, - 0.002698864465909288f, - 0.0020601250277244316f, - 0.0012156374711696657f, - 0.00025858155245919815f, - -0.0007073681818317356f, - -0.0015807551810501244f, - -0.0022693572465456347f, - -0.002706332216634437f, - -0.002847415468166842f, - -0.002685049904781321f, - -0.002241555313336647f, - -0.0015677281601321103f, - -0.0007401321025391216f, - 0.00015080284754578053f, - 0.001010469529718797f, - 0.0017482590981993708f, - 0.0022874947808971076f, - 0.0025746366972740037f, - 0.0025840361579153536f, - 0.0023191095738080646f, - 0.0018119245687932325f, - 0.0011202470135660719f, - 0.0003207533398910953f, - -0.0005001275988053344f, - -0.0012553733554405261f, - -0.0018659822196923194f, - -0.0022692307984843695f, - -0.002425430974535461f, - -0.0023218012876373207f, - -0.001973351045206107f, - -0.0014208542690678963f, - -0.0007258491765731604f, - 0.000036170113001030666f, - 0.000783301048300022f, - 0.001436104198970914f, - 0.0019268362424314605f, - 0.0022053677261764473f, - 0.0022452826272775055f, - 0.0020457879850275806f, - 0.0016308959748307101f, - 0.001047910209033917f, - 0.00036046532149158213f, - -0.0003566571442405154f, - -0.0010269473735399154f, - -0.001579698202436061f, - -0.0019576726289922586f, - -0.0021230164816546412f, - -0.002060772342951284f, - -0.0017802301804471922f, - -0.0013137753201852866f, - -0.0007131888239163173f, - -0.00004396586555373278f, - 0.0006218226272857692f, - 0.0012133648938024923f, - 0.001668583653638679f, - 0.0019405399480025168f, - 0.002002271293795752f, - 0.0018494543772917837f, - 0.0015005576753828536f, - 0.0009946245737452108f, - 0.00038702570052167175f, - -0.0002564820252824784f, - -0.0008669516848493666f, - -0.0013797190792108685f, - -0.0017410363220249359f, - -0.0019137822019732152f, - -0.0018816779265696613f, - -0.0016485023632017621f, - -0.0012439856702169039f, - -0.0007071978327354896f, - -0.00010287888971484906f, - 0.0005087929550228872f, - 0.0010620969968500548f, - 0.0014959523321337515f, - 0.0017665803736364497f, - 0.0018474313131084748f, - 0.0017300695180374488f, - 0.001427583798294352f, - 0.0009743368473658559f, - 0.0004203972508663324f, - -0.0001750373426426684f, - -0.00074891505028209f, - -0.001239958862296361f, - -0.0015955792180418218f, - -0.0017782857455891608f, - -0.0017696752225057275f, - -0.0015718235937901522f, - -0.0012066408059786802f, - -0.0007134285789517371f, - -0.00014493157532811753f, - 0.00043789676780500817f, - 0.0009725070918705811f, - 0.001401938565514821f, - 0.0016808579160732414f, - 0.0017799814611489096f, - 0.001689241246422699f, - 0.001418713685742809f, - 0.0009969768021073388f, - 0.00046927876230741587f, - -0.00010821707107113083f, - -0.0006738084260047715f, - -0.0011668823340746201f, - -0.0015352614436320124f, - -0.0017393843977854672f, - -0.0017577706864892022f, - -0.0015880719715790936f, - -0.0012479991812573157f, - -0.0007733385753572961f, - -0.00021414834948363428f, - 0.00037029761935479554f, - 0.0009178041117923993f, - 0.0013699023886079994f, - 0.00167799070404902f, - 0.0018085733127357138f, - 0.0017469004431845741f, - 0.0014984460140958136f, - 0.0010883547994854647f, - 0.0005590116846630576f, - -0.000034277486241399186f, - -0.0006290757127071664f, - -0.0011623018660948076f, - -0.0015767572410517868f, - -0.001827180245955061f, - -0.001885184212057672f, - -0.0017423327816480623f, - -0.001411151838185113f, - -0.0009242244673079876f, - -0.00033072041687547837f, - 0.00030908834306052194f, - 0.0009254779772329933f, - 0.001463759939291864f, - 0.00184518937028339f, - 0.002054368304440125f, - 0.0020485384828633777f, - 0.0018238726340835366f, - 0.0014076463254305174f, - 0.0008397052861045773f, - 0.00017203581592664565f, - -0.0005293364966829129f, - -0.0011899674040168643f, - -0.0017380160407736585f, - -0.0021136819231921474f, - -0.002274533536918245f, - -0.0021983440926621103f, - -0.0018849613428995964f, - -0.0013572018806166464f, - -0.0006600057708364918f, - 0.0001428944628485573f, - 0.0009750187684900944f, - 0.0017550758096436146f, - 0.002405247908616018f, - 0.002858826770125135f, - 0.003066845756573675f, - 0.003003052919640715f, - 0.0026667562654551246f, - 0.0020838354221374305f, - 0.0013056441129537509f, - 0.00040506185948651675f, - -0.0005294931107453994f, - -0.001401086778987431f, - -0.00211370881796881f, - -0.002579780077995427f, - -0.00272885000805846f, - -0.0025137861299055595f, - -0.0019147615431778597f, - -0.0009423273945619157f, - 0.00036406537300421297f, - 0.0019376716631701368f, - 0.003689888284206567f, - 0.005516680433668245f, - 0.007306289027941143f, - 0.008947826461137941f, - 0.010339492321850014f, - 0.011395973228693276f, - 0.012054670302231996f, - 0.01227985703081096f, - 0.012064461696610605f, - 0.011429759533610568f, - 0.010422968037142797f, - 0.009112816853143859f, - 0.007583700132903604f, - 0.005929041211240287f, - 0.004244279668461443f, - 0.002620018618160862f, - 0.0011360279713313209f, - -0.00014348647168516363f, - -0.0011732914244696805f, - -0.0019288301578398123f, - -0.0024058915455907196f, - -0.002619226598223891f, - -0.002599310268302821f, - -0.0023884346335371165f, - -0.006122920352830965f - } ), - - /* Half-band filter coefficients retrieved October 2015 from: - * https://github.com/airspy/host/libairspy/src/filters.h - * - * Copyright (C) 2014, Youssef Touil - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - ******************************************************************************/ - HALF_BAND_FILTER_47T( new float[] - { - -0.000998606272947510f, 0.0f, 0.001695637278417295f, 0.0f, - -0.003054430179754289f, 0.0f, 0.005055504379767936f, 0.0f, - -0.007901319195893647f, 0.0f, 0.011873357051047719f, 0.0f, - -0.017411159379930066f, 0.0f, 0.025304817427568772f, 0.0f, - -0.037225225204559217f, 0.0f, 0.057533286997004301f, 0.0f, - -0.102327462004259350f, 0.0f, 0.317034472508947400f, 0.5f, - 0.317034472508947400f, 0.0f, -0.102327462004259350f, 0.0f, - 0.057533286997004301f, 0.0f, -0.037225225204559217f, 0.0f, - 0.025304817427568772f, 0.0f, -0.017411159379930066f, 0.0f, - 0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f, - 0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f, - 0.001695637278417295f, 0.0f, -0.000998606272947510f - }), - - /** - * FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - * .sampleRate(100) - * .gridDensity(16) - * .oddLength(true) - * .passBandCutoff(21) - * .passBandAmplitude(1.0) - * .passBandRipple(0.01) - * .stopBandStart(29) - * .stopBandAmplitude(0.0) - * .stopBandRipple(0.01) //Approximately -85 dB suppression - * .build(); - */ - HALF_BAND_FILTER_27T(new float[] - { - 0.007495115f, 0.0f, -0.011045938f, 0.0f, - 0.019129561f, 0.0f, -0.031747308f, 0.0f, - 0.053220540f, 0.0f, -0.099569574f, 0.0f, - 0.316083550f, 0.5f, 0.316083550f, 0.0f, - -0.099569574f, 0.0f, 0.053220540f, 0.0f, - -0.031747308f, 0.0f, 0.019129561f, 0.0f, - -0.011045938f, 0.0f, 0.007495115f - - }); - - float[] mCoefficients; - - private Filters( float[] coefficients ) - { - mCoefficients = coefficients; - } - - public float[] getCoefficients() - { - return mCoefficients; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java deleted file mode 100644 index 4722d9f2..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandFilter.java +++ /dev/null @@ -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 - ******************************************************************************/ -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 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(); - 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; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java deleted file mode 100644 index 87a5f4cd..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/FloatHalfBandNoDecimateFilter.java +++ /dev/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 - ******************************************************************************/ -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 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(); - 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; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java index 6cb112e8..b0ce0805 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/GoertzelFilter.java @@ -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 ******************************************************************************/ 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 - * + *

* 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; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/Window.java b/src/main/java/io/github/dsheirer/dsp/filter/Window.java deleted file mode 100644 index c0a7e4d5..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/Window.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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 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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java index 09f6d34a..4c9a7fe0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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, ISourceEventListener +public abstract class AbstractComplexPolyphaseChannelizer implements Listener, ISourceEventListener { private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class); - private ReusableChannelResultsBufferQueue mBufferQueue = new ReusableChannelResultsBufferQueue("AbstractComplexPolyphaseChannelizer"); private Broadcaster mSourceChangeBroadcaster = new Broadcaster(); private List mChannels = new CopyOnWriteArrayList<>(); private double mSampleRate; @@ -103,17 +100,14 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener 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 getSourceEventListener() { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java index c8f797e3..80fd940f 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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 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 + public class IFFTProcessorDispatcher extends Dispatcher> { - 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 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 clearQueue() - { - Collection buffersToDispose = super.clearQueue(); - - for(ReusableChannelResultsBuffer buffer: buffersToDispose) - { - buffer.clearUserCount(); - } - - return buffersToDispose; } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java deleted file mode 100644 index 2ffe2587..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -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 implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(ContinuousBufferProcessor.class); - - protected OverflowableTransferQueue mQueue; - private Listener> 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> 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 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> 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 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 clearQueue() - { - Collection 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(); - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java deleted file mode 100644 index 9f3b9cb9..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -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 extends ContinuousBufferProcessor -{ - 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(maximumSize, resetThreshold)); - } - - /** - * Distributes queued buffers to the listener - */ - @Override - protected void process() - { - List 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."); - } - } - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java index 8d6d8941..36e8a749 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ 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 mSourceEventBroadcaster = new Broadcaster<>(); - private IReusableComplexBufferProvider mReusableBufferProvider; + private INativeBufferProvider mNativeBufferProvider; private List mChannelSources = new CopyOnWriteArrayList<>(); private ChannelCalculator mChannelCalculator; private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer; private ChannelSourceEventListener mChannelSourceEventListener = new ChannelSourceEventListener(); - private BufferSourceEventMonitor mBufferSourceEventMonitor = new BufferSourceEventMonitor(); - private ContinuousBufferProcessor mBufferProcessor; + private NativeBufferReceiver mNativeBufferReceiver = new NativeBufferReceiver(); + private Dispatcher mBufferDispatcher; private Map 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> + public class NativeBufferReceiver implements Listener { private Queue mQueuedSourceEvents = new ConcurrentLinkedQueue<>(); @@ -579,7 +578,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor } @Override - public void receive(List 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 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(); - } } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index 1645a77a..8c3b77d1 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -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 { -// 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 sLowPassFilters = new HashMap(); - private double mChannelSampleRate; private long mIndexCenterFrequency; private long mChannelFrequencyCorrection; + private Listener 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 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 listener) + public void setListener(final Listener 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 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 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; - } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java index 2ba55d27..995bf6ce 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java @@ -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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ 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); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java index 95862798..a7242e93 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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 mChannelResultsQueue; - private List mChannelResultsToProcess = new ArrayList<>(); - private int mMaxResultsToProcess; - + private Dispatcher> mChannelResultsDispatcher; + protected Listener 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 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 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 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 channelResults); @Override public int getInputChannelCount() { return mInputChannelCount; } - - /** - * Indicates if this channel has a frequency correction offset - */ - public boolean hasFrequencyCorrection() - { - return mFrequencyCorrectionEnabled; - } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java index 3b88b07e..286f6c17 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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 channelResults); + + /** + * Listener to receive assembled complex samples buffers + */ + void setListener(Listener 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 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(); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java new file mode 100644 index 00000000..f610ab84 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/MixerAssembler.java @@ -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 + * **************************************************************************** + */ + +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(); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java new file mode 100644 index 00000000..df9e1e36 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelMixerAssembler.java @@ -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 + * **************************************************************************** + */ + +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; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java index 4e7b57e0..4fe01f01 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java @@ -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 - * - ******************************************************************************/ + * **************************************************************************** + */ 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 channelIndexes, double gain) + public OneChannelOutputProcessor(double sampleRate, List 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 channelResults, - ReusableComplexBufferAssembler reusableComplexBufferAssembler) + public void process(List 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(); } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java new file mode 100644 index 00000000..af696548 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/SampleAssembler.java @@ -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 + * **************************************************************************** + */ + +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; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java new file mode 100644 index 00000000..a0f944ed --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java @@ -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 + * **************************************************************************** + */ + +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; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 780d136e..e6b7d0dc 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ 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 channelIndexes, float[] filter, double gain) + public TwoChannelOutputProcessor(double sampleRate, List 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 channelResultsBuffers, - ReusableComplexBufferAssembler reusableComplexBufferAssembler) + public void process(List 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); + } + } + } } } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java deleted file mode 100644 index 7093b939..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/cic/ComplexPrimeCICDecimate.java +++ /dev/null @@ -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 - * ***************************************************************************** - */ -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 -{ -// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class); - - private static Map sLowPassFilters = new HashMap(); - private static Map> sPrimeFactors = new HashMap(); - - private List mDecimatingStages = new ArrayList(); - 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 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 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 getPrimeFactors(int decimation) - { - List 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 mStages = new ArrayList(); - 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 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() - { - @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 listener) - { - mReusableComplexBufferListener = listener; - - if(mReusableComplexBufferListener == null) - { - removeListener(); - } - } - - public void removeListener() - { - mReusableComplexBufferListener = new Listener() - { - @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; - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java new file mode 100644 index 00000000..e7e0910c --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/cic/PrimeCicDecimationFilter.java @@ -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 + * **************************************************************************** + */ +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 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(); + } + + + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java b/src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java deleted file mode 100644 index 1acd545d..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/cic/RealPrimeCICDecimate.java +++ /dev/null @@ -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 - ******************************************************************************/ -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 mDecimatingStages = new ArrayList(); - 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 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 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 mStages = new ArrayList(); - 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 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() - { - @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 listener) - { - mReusableBufferListener = listener; - } - - /** - * Removes the listener from receiving output samples - */ - public void removeListener() - { - mReusableBufferListener = null; - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java deleted file mode 100644 index 03224bc2..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/correction/IQCorrectionFilter.java +++ /dev/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 - * - ******************************************************************************/ -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; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java deleted file mode 100644 index 9d12e45d..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/dc/BufferDCFilter.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -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; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java deleted file mode 100644 index 8a54141f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/dc/DCRemovalFilter.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -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; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java new file mode 100644 index 00000000..a5db586e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/DcRemovalFilterFactory.java @@ -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 + * **************************************************************************** + */ + +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); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java new file mode 100644 index 00000000..b5ec0adf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/IDcRemovalFilter.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.dc; + +/** + * Interface for a DC removal filter. + */ +public interface IDcRemovalFilter +{ + float[] filter(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java index b980b02f..70aa35a8 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/IIRSinglePoleDCRemovalFilter.java @@ -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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ 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; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java new file mode 100644 index 00000000..6fab6f56 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/ScalarDcRemovalFilter.java @@ -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 + * **************************************************************************** + */ + +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); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java new file mode 100644 index 00000000..33cc14b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/dc/VectorDcRemovalFilter.java @@ -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 + * **************************************************************************** + */ + +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 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!"); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java deleted file mode 100644 index 01c067e4..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX1024Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java deleted file mode 100644 index c6aac08e..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX128Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java deleted file mode 100644 index a6204a0f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX16Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java deleted file mode 100644 index edf62cda..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX256Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java deleted file mode 100644 index 6adf9721..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX2Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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); - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java deleted file mode 100644 index b3edae7a..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX32Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java deleted file mode 100644 index 738bd74a..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX4Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java deleted file mode 100644 index ef9d5472..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX512Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java deleted file mode 100644 index 26152ec2..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX64Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java deleted file mode 100644 index 42477c9c..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX8Filter.java +++ /dev/null @@ -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 - * **************************************************************************** - */ - -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)); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java index d8d5a750..e82337f5 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java @@ -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; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java index 5a4fba7c..030d8c5c 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX1024Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java index faf2da51..b6579a5c 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX128Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java index ca732a0b..5f9296c6 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX16Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java index e72e3d36..98e15199 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX256Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java index c62daef8..9cc41832 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX2Filter.java @@ -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); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java index 86227039..1a6bc192 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX32Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java index 5461cf91..469beae1 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX4Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java index 7d2882ef..70e78dfb 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX512Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java index a2ba8c87..d18bff5d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX64Filter.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java index 8feb9318..89267400 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/RealDecimateX8Filter.java @@ -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 8 filter for real valued sample buffers. @@ -30,16 +29,16 @@ public class RealDecimateX8Filter extends RealDecimateX4Filter { private static final int VALIDATION_LENGTH = 8; private static final int DECIMATE_BY_8_FILTER_LENGTH = 15; - private static final Window.WindowType DECIMATE_BY_8_WINDOW_TYPE = Window.WindowType.BLACKMAN; - private RealHalfBandDecimationFilter mFilter; + private static final WindowType DECIMATE_BY_8_WINDOW_TYPE = WindowType.BLACKMAN; + private IRealDecimationFilter mFilter; /** * Constructs the decimation filter. */ public RealDecimateX8Filter() { - mFilter = new RealHalfBandDecimationFilter(FilterFactory.getHalfBand(DECIMATE_BY_8_FILTER_LENGTH, - DECIMATE_BY_8_WINDOW_TYPE)); + mFilter = FilterFactory.getRealDecimationFilter(DECIMATE_BY_8_FILTER_LENGTH, + DECIMATE_BY_8_WINDOW_TYPE); } @Override diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java index c4062976..a587b659 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java @@ -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,8 +20,8 @@ package io.github.dsheirer.dsp.filter.design; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; +import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; +import io.github.dsheirer.dsp.window.WindowType; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -59,11 +59,13 @@ public class FilterViewer extends Application */ private float[] getFilter() { - int filterLength = 63; - Window.WindowType windowType = Window.WindowType.HAMMING; + int filterLength = 47; + WindowType windowType = WindowType.HAMMING; float[] taps = FilterFactory.getHalfBand(filterLength, windowType); + taps = HilbertTransform.HALF_BAND_FILTER_47_TAP; + if(taps == null) { throw new IllegalStateException("Couldn't design filter"); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java b/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java index 14c4dee3..763c695f 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/HalfBandDecimationFilterAnalysis.java @@ -20,7 +20,7 @@ package io.github.dsheirer.dsp.filter.design; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; +import io.github.dsheirer.dsp.window.WindowType; import org.apache.commons.math3.util.FastMath; import org.jtransforms.fft.FloatFFT_1D; @@ -35,7 +35,7 @@ public class HalfBandDecimationFilterAnalysis private static FloatFFT_1D FFT = new FloatFFT_1D(FFT_SIZE); private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0"); - private static final Window.WindowType WINDOW_TYPE = Window.WindowType.BLACKMAN; + private static final WindowType WINDOW_TYPE = WindowType.BLACKMAN; private static final int[] TAP_LENGTHS = new int[]{7,11,15,19,23,27,31,35,39,43,47,51,55,59,63,67,71,75,79,83,87,91,95,99,103,107}; private static final int BY_2 = (int)(FFT_SIZE / 2 * (1.0 / 2.0)); private static final int BY_2_25 = (int)(FFT_SIZE / 2 * (1.0 / 2.25)); @@ -58,7 +58,7 @@ public class HalfBandDecimationFilterAnalysis public static void analyze() { - for(Window.WindowType windowType: Window.NO_PARAMETER_WINDOWS) + for(WindowType windowType: WindowType.NO_PARAMETER_WINDOWS) { StringBuilder sb = new StringBuilder(); sb.append("Window: " + windowType).append("\n"); @@ -133,7 +133,7 @@ public class HalfBandDecimationFilterAnalysis * @param windowType to apply to the taps during filter creation * @return filter */ - private static float[] getTaps(int filterLength, Window.WindowType windowType) + private static float[] getTaps(int filterLength, WindowType windowType) { return FilterFactory.getHalfBand(filterLength, windowType); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java deleted file mode 100644 index 7c618c00..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter2.java +++ /dev/null @@ -1,138 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.filter.fir.complex; - -import io.github.dsheirer.dsp.filter.fir.FIRFilter; -import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; - -public class ComplexFIRFilter2 extends FIRFilter -{ - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("Complex FIR Filter"); - private RealFIRFilter2 mIFilter; - private RealFIRFilter2 mQFilter; - - /** - * Complex FIR Filter for processing complex sample pairs that internally uses two RealFIRFilter - * instances. - * - * @param coefficients - filter taps - * @param gain - gain to apply to filtered outputs - use 1.0f for no gain - */ - public ComplexFIRFilter2(float[] coefficients, float gain) - { - mIFilter = new RealFIRFilter2(coefficients, gain); - mQFilter = new RealFIRFilter2(coefficients, gain); - } - - /** - * Complex FIR Filter for processing complex sample pairs that internally uses two RealFIRFilter - * instances. This constructor uses a default gain of 1.0f. - * - * @param coefficients - filter taps - */ - public ComplexFIRFilter2(float[] coefficients) - { - this(coefficients, 1.0f); - } - - /** - * Filters the inphase sample value. - * @param sample to filter - * @return filtered sample - */ - public float filterInphase(float sample) - { - return mIFilter.filter(sample); - } - - /** - * Current filtered inphase sample value after invoking the filterInphase() method. - */ - public float currentInphaseValue() - { - return mIFilter.currentValue(); - } - - /** - * Filters the quadrature sample value. - * @param sample to filter - * @return filtered sample - */ - public float filterQuadrature(float sample) - { - return mQFilter.filter(sample); - } - - /** - * Current filtered quadrature sample value after invoking the filterInphase() method. - */ - public float currentQuadratureValue() - { - return mQFilter.currentValue(); - } - - /** - * Filters the complex sample. - * @param sample to filter - * @return filtered sample - */ - public Complex filter(Complex sample) - { - float i = filterInphase(sample.inphase()); - float q = filterQuadrature(sample.quadrature()); - - return new Complex(i, q); - } - - /** - * Filters the complex samples from the reusable buffer and returns a new complex buffer with the filtered output - * - * Note: the original reusable buffer user count is decremented and a new reusable buffer is returned - * with the user count already incremented to one. - * - * @param originalBuffer with complex samples to filter - * @return new buffer containing filtered complex samples - */ - public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer) - { - ReusableComplexBuffer filteredBuffer = mReusableComplexBufferQueue.getBuffer(originalBuffer.getSamples().length); - filteredBuffer.setTimestamp(originalBuffer.getTimestamp()); - - float[] samples = originalBuffer.getSamples(); - float[] filteredSamples = filteredBuffer.getSamples(); - - for(int x = 0; x < originalBuffer.getSamples().length; x += 2) - { - filteredSamples[x] = filterInphase(samples[x]); - filteredSamples[x + 1] = filterQuadrature(samples[x + 1]); - } - - originalBuffer.decrementUserCount(); - - return filteredBuffer; - } - - @Override - public void dispose() - { - mIFilter.dispose(); - mQFilter.dispose(); - mReusableComplexBufferQueue.dispose(); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java new file mode 100644 index 00000000..aac7f9c2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/IComplexFilter.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.fir.complex; + +/** + * Interface for a filter for float array sample buffers + */ +public interface IComplexFilter +{ + float[] filter(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java new file mode 100644 index 00000000..ae154d87 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ScalarComplexFIRFilter.java @@ -0,0 +1,152 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.fir.complex; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.window.WindowType; +import org.apache.commons.lang3.ArrayUtils; + +import java.text.DecimalFormat; +import java.util.Random; + +public class ScalarComplexFIRFilter implements IComplexFilter +{ + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public ScalarComplexFIRFilter(float[] coefficients) + { + //Reverse the order of the coefficients. + ArrayUtils.reverse(coefficients); + + //Double the size of the coefficients array to align with the complex samples array + mCoefficients = new float[coefficients.length * 2]; + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + } + + mBufferOverlap = mCoefficients.length - 2; + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + int offset = 0; + + float iAccumulator, qAccumulator; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + iAccumulator = 0.0f; + qAccumulator = 0.0f; + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += 2) + { + offset = bufferPointer + coefficientPointer; + iAccumulator += mBuffer[offset] * mCoefficients[coefficientPointer]; + qAccumulator += mBuffer[offset + 1] * mCoefficients[coefficientPointer]; + } + + filtered[bufferPointer] = iAccumulator; + filtered[bufferPointer + 1] = qAccumulator; + } + + return filtered; + } + + public static void main(String[] args) + { + Random random = new Random(); + + int sampleSize = 2048; + + float[] samples = new float[sampleSize]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + float[] coefficients = FilterFactory.getLowPass(1000, 250, 99, WindowType.BLACKMAN); + +// ComplexFIRFilter2 legacyFilter = new ComplexFIRFilter2(coefficients); + ScalarComplexFIRFilter filter = new ScalarComplexFIRFilter(coefficients); + VectorComplexFIRFilter128Bit vectorFilter = new VectorComplexFIRFilter128Bit(coefficients); + + double accumulator = 0.0d; + + int iterations = 1_000_000; + + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { +// float[] filtered = legacyFilter.filter(samples); +// float[] filtered = filter.filter(samples); + float[] filtered = vectorFilter.filter(samples); +// float[] vfiltered = vectorFilter.filter(samples); + accumulator += filtered[3]; + } + +// System.out.println("REG:" + Arrays.toString(filtered)); +// System.out.println("VEC:" + Arrays.toString(vfiltered)); + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java new file mode 100644 index 00000000..1ff4e32f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter128Bit.java @@ -0,0 +1,124 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.complex; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorComplexFIRFilter128Bit implements IComplexFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final VectorMask I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES); + private static final VectorMask Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES); + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorComplexFIRFilter128Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length * 2)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients with each tap duplicated for complex filtering + ArrayUtils.reverse(coefficients); + + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + mCoefficients[2 * x + 1] = coefficients[x]; + } + + //Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2); + + //Set to non-null and we'll resize on the first buffer. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK); + filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java new file mode 100644 index 00000000..6515bb06 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilter64Bit.java @@ -0,0 +1,124 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.complex; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorComplexFIRFilter64Bit implements IComplexFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final VectorMask I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES); + private static final VectorMask Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES); + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorComplexFIRFilter64Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length * 2)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients with each tap duplicated for complex filtering + ArrayUtils.reverse(coefficients); + + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + mCoefficients[2 * x + 1] = coefficients[x]; + } + + //Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2); + + //Set to non-null and we'll resize on the first buffer. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK); + filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java new file mode 100644 index 00000000..4a45dbcf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/VectorComplexFIRFilterDefaultBit.java @@ -0,0 +1,124 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.complex; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorComplexFIRFilterDefaultBit implements IComplexFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final VectorMask I_VECTOR_MASK = VectorUtilities.getIVectorMask(VECTOR_SPECIES); + private static final VectorMask Q_VECTOR_MASK = VectorUtilities.getQVectorMask(VECTOR_SPECIES); + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorComplexFIRFilterDefaultBit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length * 2)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients with each tap duplicated for complex filtering + ArrayUtils.reverse(coefficients); + + for(int x = 0; x < coefficients.length; x++) + { + mCoefficients[2 * x] = coefficients[x]; + mCoefficients[2 * x + 1] = coefficients[x]; + } + + //Set buffer overlap to larger of the length of the SIMD lanes minus 2 or double the coefficients length minus 2 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 2, (coefficients.length * 2) - 2); + + //Set to non-null and we'll resize on the first buffer. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD, I_VECTOR_MASK); + filtered[bufferPointer + 1] = accumulator.reduceLanes(VectorOperators.ADD, Q_VECTOR_MASK); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java new file mode 100644 index 00000000..41db8ddc --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/IRealFilter.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.fir.real; + +/** + * Interface for a filter for float array sample buffers + */ +public interface IRealFilter +{ + float[] filter(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java new file mode 100644 index 00000000..82e6c5cf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.real; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.window.WindowType; +import org.apache.commons.lang3.ArrayUtils; + +import java.text.DecimalFormat; +import java.util.Random; + +/** + * Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays. + * + * Note: filtering operations in this class are structured to leverage SIMD processor intrinsics when + * available to the Java runtime. + */ +public class RealFIRFilter implements IRealFilter +{ + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public RealFIRFilter(float[] coefficients) + { + //Reverse the order of the coefficients. + mCoefficients = coefficients; + ArrayUtils.reverse(mCoefficients); + mBufferOverlap = mCoefficients.length - 1; + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[mCoefficients.length]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer++) + { + filtered[bufferPointer] += mBuffer[bufferPointer + coefficientPointer] * mCoefficients[coefficientPointer]; + } + } + + return filtered; + } + + public static void main(String[] args) + { + Random random = new Random(); + + int sampleSize = 2048; + + float[] samples = new float[sampleSize]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + float[] coefficients = FilterFactory.getLowPass(1000, 250, 99, WindowType.BLACKMAN); + + RealFIRFilter filter = new RealFIRFilter(coefficients); + VectorRealFIRFilter256Bit vectorFilter = new VectorRealFIRFilter256Bit(coefficients); + + double accumulator = 0.0d; + + int iterations = 1_000_000; + + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { + float[] filtered = filter.filter(samples); +// float[] filtered = vectorFilter.filter(samples); +// float[] vfiltered = vectorFilter.filter(samples); + accumulator += filtered[3]; + } + +// System.out.println("REG:" + Arrays.toString(filtered)); +// System.out.println("VEC:" + Arrays.toString(vfiltered)); + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java deleted file mode 100644 index ee866c96..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/RealFIRFilter2.java +++ /dev/null @@ -1,132 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.filter.fir.real; - -import io.github.dsheirer.dsp.filter.fir.FIRFilter; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -/** - * Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays. - * - * Note: filtering operations in this class are structured to leverage SIMD processor intrinsics when - * available to the Java runtime. - */ -public class RealFIRFilter2 extends FIRFilter -{ - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealFIRFilter2"); - - private float[] mData; - private float[] mCoefficients; - private float mGain; - private float mAccumulator; - - /** - * Float sample FIR filter base class. - * - * @param coefficients - filter coefficients in normal order. - * @param gain value to apply to the filtered output. Use 1.0f for no gain - */ - public RealFIRFilter2(float[] coefficients, float gain) - { - mGain = gain; - mCoefficients = coefficients; - mData = new float[coefficients.length]; - } - - /** - * Float sample FIR filter base class that uses a default gain value of 1.0f. - * - * @param coefficients - filter coefficients in normal order. - */ - public RealFIRFilter2(float[] coefficients) - { - this(coefficients, 1.0f); - } - - /** - * Disposes this filter to prepare for garbage collection. - */ - @Override - public void dispose() - { - mCoefficients = null; - mData = null; - } - - /** - * Filters the sample argument. Loads the sample into the internal data buffer and performs - * convolution between the sample buffer and the filter coefficients. - * - * @param sample to load - * @return filtered value - */ - public float filter(float sample) - { - //Use array copy to leverage SIMD intrinsics - System.arraycopy(mData, 0, mData, 1, mData.length - 1); - mData[0] = sample; - - mAccumulator = 0.0f; - - //Use vector dot product to leverage SIMD intrinsics - for(int x = 0; x < mCoefficients.length; x++) - { - mAccumulator = Math.fma(mData[x], mCoefficients[x], mAccumulator); - } - - /* Apply gain and return the filtered value */ - mAccumulator *= mGain; - - return mAccumulator; - } - - /** - * Current filtered output value for the filter after the filter() method has been invoked. - */ - public float currentValue() - { - return mAccumulator; - } - - - /** - * Filters the samples contained in the unfilteredBuffer and returns a new reusable buffer with the - * filtered samples. - * - * Note: user count on the returned (new) buffer is set to one and the user count is decremented on - * the unfiltered buffer argument. - * - * @param unfilteredBuffer containing a sample array to be filtered - * @return a new reusable buffer with the filtered samples. - */ - public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer) - { - float[] unfilteredSamples = unfilteredBuffer.getSamples(); - - ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length); - float[] filteredSamples = filteredBuffer.getSamples(); - - for(int x = 0; x < unfilteredSamples.length; x++) - { - filteredSamples[x] = filter(unfilteredSamples[x]); - } - - unfilteredBuffer.decrementUserCount(); - - return filteredBuffer; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java new file mode 100644 index 00000000..52a86bce --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter128Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter128Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter128Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java new file mode 100644 index 00000000..24c8190e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter256Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter256Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter256Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java new file mode 100644 index 00000000..7688380f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter512Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter512Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter512Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java new file mode 100644 index 00000000..de53d409 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilter64Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilter64Bit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilter64Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java new file mode 100644 index 00000000..be40db4d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/real/VectorRealFIRFilterDefaultBit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.fir.real; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Finite Impulse Response (FIR) filter for filtering float sample arrays. + * Note: this filter uses Project Panama SIMD instructions available in JDK 17+ + */ +public class VectorRealFIRFilterDefaultBit implements IRealFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float[] mBuffer; + private float[] mCoefficients; + private int mBufferOverlap; + + /** + * Float sample FIR filter base class. + * + * @param coefficients - filter coefficients in normal order. + */ + public VectorRealFIRFilterDefaultBit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < (coefficients.length)) + { + arrayLength += VECTOR_SPECIES.length(); + } + + //Reverse the order of the coefficients and load them into the oversized/aligned coefficients buffer + ArrayUtils.reverse(coefficients); + mCoefficients = new float[arrayLength]; + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficient's length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + + //We'll resize this later when we get the first sample buffer. For now, make it non-null. + mBuffer = new float[arrayLength]; + } + + /** + * Filters the sample array + * @param samples to filter + * @return filtered samples + */ + public float[] filter(float[] samples) + { + int bufferLength = samples.length + mBufferOverlap; + + //Resize the data buffer if needed. This shouldn't happen more than once since all buffers should be same size + if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of previous buffer to the beginning of the new temp buffer and reassign + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array to end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java new file mode 100644 index 00000000..82210c3b --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/RealHalfBandDecimationFilter.java @@ -0,0 +1,160 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.window.WindowType; + +import java.text.DecimalFormat; +import java.util.Random; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when + * supported by the host CPU. + * + * Note: this class is structured to process an entire float array, versus processing one sample at a time from the + * array. + */ +public class RealHalfBandDecimationFilter implements IRealDecimationFilter +{ + private static final float CENTER_COEFFICIENT = 0.5f; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public RealHalfBandDecimationFilter(float[] coefficients) + { + if((coefficients.length + 1) % 4 != 0) + { + throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]"); + } + + mCoefficients = coefficients; + mBufferOverlap = mCoefficients.length - 1; + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + float accumulator = 0.0f; + int half = mBufferOverlap / 2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = 0.0f; + + for(int coefficientPointer = 0; coefficientPointer < half; coefficientPointer += 2) + { + //Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by + //one of the coefficients to achieve the same effect. + accumulator += mCoefficients[coefficientPointer] * + (mBuffer[bufferPointer + coefficientPointer] + + mBuffer[bufferPointer + (mBufferOverlap - coefficientPointer)]); + } + + accumulator += mBuffer[bufferPointer + half] * CENTER_COEFFICIENT; + + filtered[bufferPointer / 2] = accumulator; + } + + return filtered; + } + + public static void main(String[] args) + { + Random random = new Random(); + + int sampleSize = 2048; + + float[] samples = new float[sampleSize]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN); + +// RealHalfBandDecimationFilter filter = new RealHalfBandDecimationFilter(coefficients); +// VectorRealHalfBandDecimationFilter63Tap64Bit vectorFilter64 = new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients); +// VectorRealHalfBandDecimationFilter63Tap128Bit vectorFilter128 = new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients); +// VectorRealHalfBandDecimationFilter63Tap256Bit vectorFilter256 = new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients); +// VectorRealHalfBandDecimationFilter23Tap512Bit vectorFilter512 = new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients); + VectorRealHalfBandDecimationFilter256Bit vectorFilter = new VectorRealHalfBandDecimationFilter256Bit(coefficients); + + double accumulator = 0.0d; + + int iterations = 10_000_000; + + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { +// float[] filtered = filter.decimateReal(samples); +// float[] filtered = vectorFilter256.decimateReal(samples); + float[] filtered = vectorFilter.decimateReal(samples); +// float[] vfiltered = vectorFilterGeneric.decimateReal(samples); + accumulator += filtered[3]; + } + +// System.out.println("REG:" + Arrays.toString(filtered)); +// System.out.println("VEC:" + Arrays.toString(vfiltered)); + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java new file mode 100644 index 00000000..0068225d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap128Bit.java @@ -0,0 +1,115 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter11Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[12]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter11Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + + FloatVector product1, product2, product3; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + filtered[bufferPointer / 2] = product1.add(product2).add(product3).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java new file mode 100644 index 00000000..aad736bf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap256Bit.java @@ -0,0 +1,112 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter11Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter11Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + + FloatVector product1, product2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 3)); + filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/RealHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap512Bit.java similarity index 51% rename from src/main/java/io/github/dsheirer/dsp/filter/halfband/real/RealHalfBandDecimationFilter.java rename to src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap512Bit.java index 53d99569..6bf03c6e 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/RealHalfBandDecimationFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap512Bit.java @@ -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 @@ -16,29 +16,30 @@ * along with this program. If not, see * **************************************************************************** */ -package io.github.dsheirer.dsp.filter.halfband.real; +package io.github.dsheirer.dsp.filter.halfband; import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; /** * Implements a half-band filter that produces one filtered output for every two input samples. * - * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when - * supported by the host CPU. + * This filter uses the Java Vector API for SIMD available in JDK 17+. * - * Note: this class is structured to process an entire float array, versus processing one sample at a time from the - * array. + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. */ -public class RealHalfBandDecimationFilter implements IRealDecimationFilter +public class VectorRealHalfBandDecimationFilter11Tap512Bit implements IRealDecimationFilter { - private static final float CENTER_COEFFICIENT = 0.5f; - private float[] mCoefficients; + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[16]; private float[] mBuffer; - private float mAccumulator; - private int mCoefficientPointer; - private int mCoefficientsLengthMinus1; - private int mBufferPointer; - private int mHalf; + private int mBufferOverlap; /** * Creates a half band filter with inherent decimation by two. @@ -46,16 +47,22 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are * zero valued except for the middle odd index coefficient which should be valued 0.5 */ - public RealHalfBandDecimationFilter(float[] coefficients) + public VectorRealHalfBandDecimationFilter11Tap512Bit(float[] coefficients) { - if((coefficients.length + 1) % 4 != 0) + if(coefficients.length != COEFFICIENTS_LENGTH) { - throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]"); + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); } - mCoefficients = coefficients; - mCoefficientsLengthMinus1 = mCoefficients.length - 1; - mHalf = mCoefficientsLengthMinus1 / 2; + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); } public float[] decimateReal(float[] samples) @@ -65,7 +72,7 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); } - int bufferLength = samples.length + mCoefficientsLengthMinus1; + int bufferLength = samples.length + mBufferOverlap; if(mBuffer == null) { @@ -75,36 +82,28 @@ public class RealHalfBandDecimationFilter implements IRealDecimationFilter { float[] temp = new float[bufferLength]; //Move residual samples from end of old buffer to the beginning of the new temp buffer - System.arraycopy(mBuffer, mBuffer.length - mCoefficientsLengthMinus1, temp, 0, mCoefficientsLengthMinus1); + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); mBuffer = temp; } else { //Move residual samples from end of buffer to the beginning of the buffer - System.arraycopy(mBuffer, samples.length, mBuffer, 0, mCoefficientsLengthMinus1); + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); } //Copy new sample array into end of buffer - System.arraycopy(samples, 0, mBuffer, mCoefficientsLengthMinus1, samples.length); + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); float[] filtered = new float[samples.length / 2]; - for(mBufferPointer = 0; mBufferPointer < samples.length; mBufferPointer += 2) + FloatVector filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + + FloatVector product; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) { - mAccumulator = 0.0f; - - for(mCoefficientPointer = 0; mCoefficientPointer < mHalf; mCoefficientPointer += 2) - { - //Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by - //one of the coefficients to achieve the same effect. - mAccumulator += mCoefficients[mCoefficientPointer] * - (mBuffer[mBufferPointer + mCoefficientPointer] + - mBuffer[mBufferPointer + (mCoefficientsLengthMinus1 - mCoefficientPointer)]); - } - - mAccumulator += mBuffer[mBufferPointer + mHalf] * CENTER_COEFFICIENT; - - filtered[mBufferPointer / 2] = mAccumulator; + product = filter.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + filtered[bufferPointer / 2] = product.reduceLanes(VectorOperators.ADD); } return filtered; diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java new file mode 100644 index 00000000..ecbcc1df --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter11Tap64Bit.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 11-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter11Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 11; + + private float[] mCoefficients = new float[12]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter11Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + + FloatVector product1, product2, product3, product4, product5, product6; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10)); + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java new file mode 100644 index 00000000..87c251ae --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter128Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 128-bit/4-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter128Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java new file mode 100644 index 00000000..18932032 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap128Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + + FloatVector product1, product2, product3, product4; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java new file mode 100644 index 00000000..41a32c92 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap256Bit.java @@ -0,0 +1,112 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + + FloatVector product1, product2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java new file mode 100644 index 00000000..b34294b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap512Bit.java @@ -0,0 +1,111 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap512Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + + FloatVector product; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product = filter.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + filtered[bufferPointer / 2] = product.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java new file mode 100644 index 00000000..e5a358f8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter15Tap64Bit.java @@ -0,0 +1,127 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 15-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter15Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 15; + + private float[] mCoefficients = new float[16]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter15Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + FloatVector filter7 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filter8 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14); + + FloatVector product1, product2, product3, product4, product5, product6, product7, product8; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10)); + product7 = filter7.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + product8 = filter8.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 14)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).add(product7).add(product8).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java new file mode 100644 index 00000000..bab951a0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap128Bit.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[24]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + + FloatVector product1, product2, product3, product4, product5, product6; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 20)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java new file mode 100644 index 00000000..91fa52cf --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap256Bit.java @@ -0,0 +1,115 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[24]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + FloatVector product1, product2, product3; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java new file mode 100644 index 00000000..f0704b0a --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap512Bit.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[32]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap512Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + int laneMultiples = VECTOR_SPECIES.length() * (coefficients.length / VECTOR_SPECIES.length() + 1); + mBufferOverlap = Math.max(laneMultiples - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + FloatVector product1, product2; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + filtered[bufferPointer / 2] = product1.add(product2).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java new file mode 100644 index 00000000..97286c50 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter23Tap64Bit.java @@ -0,0 +1,137 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 23-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter23Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 23; + + private float[] mCoefficients = new float[24]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter23Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() -1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector filter1 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filter2 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filter3 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filter4 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filter5 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filter6 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + FloatVector filter7 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filter8 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14); + FloatVector filter9 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filter10 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 18); + FloatVector filter11 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + FloatVector filter12 = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 22); + + FloatVector product1, product2, product3, product4, product5, product6, product7, product8; + FloatVector product9, product10, product11, product12; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + product1 = filter1.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer)); + product2 = filter2.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 2)); + product3 = filter3.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 4)); + product4 = filter4.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 6)); + product5 = filter5.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 8)); + product6 = filter6.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 10)); + product7 = filter7.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 12)); + product8 = filter8.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 14)); + product9 = filter9.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 16)); + product10 = filter10.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 18)); + product11 = filter11.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 20)); + product12 = filter12.mul(FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + 22)); + + filtered[bufferPointer / 2] = product1.add(product2).add(product3).add(product4).add(product5) + .add(product6).add(product7).add(product8).add(product9).add(product10).add(product11) + .add(product12).reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java new file mode 100644 index 00000000..b725780e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter256Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 256-bit/8-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter256Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java new file mode 100644 index 00000000..e05bec7b --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter512Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 512-bit/16-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter512Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java new file mode 100644 index 00000000..ef863db3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap128Bit.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 128-bit/4-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap128Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap128Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java new file mode 100644 index 00000000..d9139180 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap256Bit.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 256-bit/8-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap256Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap256Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java new file mode 100644 index 00000000..9ca9acee --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap512Bit.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 512-bit/16-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap512Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap512Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java new file mode 100644 index 00000000..0a49b7ca --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter63Tap64Bit.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter is optimized for an 63-tap half band filter running on a CPU that supports 64-bit/2-lane floating + * point SIMD instructions. + */ +public class VectorRealHalfBandDecimationFilter63Tap64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private static final int COEFFICIENTS_LENGTH = 63; + + private float[] mCoefficients = new float[64]; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter63Tap64Bit(float[] coefficients) + { + if(coefficients.length != COEFFICIENTS_LENGTH) + { + throw new IllegalArgumentException("Half-band filter coefficients must be " + COEFFICIENTS_LENGTH + + " taps. You supplied a filter with " + coefficients.length + " taps."); + } + + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes (16) minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(VECTOR_SPECIES.length() - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < 64; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java new file mode 100644 index 00000000..ffb82a5f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilter64Bit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any tap length and uses SIMD 64-bit/2-lane instructions. + */ +public class VectorRealHalfBandDecimationFilter64Bit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilter64Bit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java new file mode 100644 index 00000000..be317504 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/halfband/VectorRealHalfBandDecimationFilterDefaultBit.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.halfband; + +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a half-band filter that produces one filtered output for every two input samples. + * + * This filter uses the Java Vector API for SIMD available in JDK 17+. + * + * This filter can be used for any combination of tap length and SIMD lane lengths. + */ +public class VectorRealHalfBandDecimationFilterDefaultBit implements IRealDecimationFilter +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float[] mCoefficients; + private float[] mBuffer; + private int mBufferOverlap; + + /** + * Creates a half band filter with inherent decimation by two. + * + * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are + * zero valued except for the middle odd index coefficient which should be valued 0.5 + */ + public VectorRealHalfBandDecimationFilterDefaultBit(float[] coefficients) + { + VectorUtilities.checkSpecies(VECTOR_SPECIES); + + //Size the coefficients array to a multiple of the vector species length, large enough to hold the taps; + int arrayLength = VECTOR_SPECIES.length(); + + while(arrayLength < coefficients.length) + { + arrayLength += VECTOR_SPECIES.length(); + } + + mCoefficients = new float[arrayLength]; + + //Copy the coefficients + System.arraycopy(coefficients, 0, mCoefficients, 0, coefficients.length); + + //Set buffer overlap to larger of the length of the SIMD lanes minus 1 or the coefficients length minus 1 + //to ensure we don't get an index out of bounds exception when loading samples from the buffer. + mBufferOverlap = Math.max(arrayLength - 1, coefficients.length - 1); + } + + public float[] decimateReal(float[] samples) + { + if(samples.length % 2 != 0) + { + throw new IllegalArgumentException("Samples array length must be an integer multiple of 2"); + } + + int bufferLength = samples.length + mBufferOverlap; + + if(mBuffer == null) + { + mBuffer = new float[bufferLength]; + } + else if(mBuffer.length != bufferLength) + { + float[] temp = new float[bufferLength]; + //Move residual samples from end of old buffer to the beginning of the new temp buffer + System.arraycopy(mBuffer, mBuffer.length - mBufferOverlap, temp, 0, mBufferOverlap); + mBuffer = temp; + } + else + { + //Move residual samples from end of buffer to the beginning of the buffer + System.arraycopy(mBuffer, samples.length, mBuffer, 0, mBufferOverlap); + } + + //Copy new sample array into end of buffer + System.arraycopy(samples, 0, mBuffer, mBufferOverlap, samples.length); + + float[] filtered = new float[samples.length / 2]; + + FloatVector accumulator, buffer, filter; + + for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer += 2) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int coefficientPointer = 0; coefficientPointer < mCoefficients.length; coefficientPointer += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, coefficientPointer); + buffer = FloatVector.fromArray(VECTOR_SPECIES, mBuffer, bufferPointer + coefficientPointer); + accumulator = filter.fma(buffer, accumulator); + } + + filtered[bufferPointer / 2] = accumulator.reduceLanes(VectorOperators.ADD); + } + + return filtered; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java deleted file mode 100644 index b13ae60c..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandDecimationFilter.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * ***************************************************************************** - * 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 - * **************************************************************************** - */ - -package io.github.dsheirer.dsp.filter.halfband.complex; - -import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -/** - * Complex half-band filter that processes samples on a per-array basis, versus a per-sample basis. - */ -public class ComplexHalfBandDecimationFilter implements IComplexDecimationFilter -{ - private static final float CENTER_COEFFICIENT = 0.5f; - private float[] mCoefficients; - private float[] mBuffer; - private float mIAccumulator; - private float mQAccumulator; - private int mCoefficientPointer; - private int mCoefficientsLengthMinus2; - private int mBufferPointer; - private int mHalf; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("half-band decimation filter"); - - /** - * Constructs a complex sample half-band decimate x2 filter using the specified filter coefficients. - * @param coefficients for the half-band filter where the middle coefficient is 0.5, and the even-numbered - * coefficients are non-zero and symmetrical, and the odd-numbered coefficients are all zero valued. - */ - public ComplexHalfBandDecimationFilter(float[] coefficients) - { - if((coefficients.length + 1) % 4 != 0) - { - throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and " + - "symmetrical where L = [x * 4 - 1]"); - } - - mCoefficients = new float[coefficients.length * 2]; - for(int x = 0; x < coefficients.length; x++) - { - mCoefficients[2 * x] = coefficients[x]; - mCoefficients[2 * x + 1] = coefficients[x]; - } - - mCoefficientsLengthMinus2 = mCoefficients.length - 2; - mHalf = mCoefficients.length / 2 - 1; - } - - public float[] decimateComplex(float[] samples) - { - if(samples.length % 4 != 0) - { - throw new IllegalArgumentException("Samples array length must be an integer multiple of 4"); - } - - int bufferLength = samples.length + mCoefficientsLengthMinus2; - - if(mBuffer == null) - { - mBuffer = new float[bufferLength]; - } - else if(mBuffer.length != bufferLength) - { - float[] temp = new float[bufferLength]; - //Move residual samples from end of old buffer to the beginning of the new temp buffer - System.arraycopy(mBuffer, mBuffer.length - mCoefficientsLengthMinus2, temp, 0, mCoefficientsLengthMinus2); - mBuffer = temp; - } - else - { - //Move residual samples from end of buffer to the beginning of the buffer - System.arraycopy(mBuffer, samples.length, mBuffer, 0, mCoefficientsLengthMinus2); - } - - //Copy new sample array into end of buffer - System.arraycopy(samples, 0, mBuffer, mCoefficientsLengthMinus2, samples.length); - - float[] filtered = new float[samples.length / 2]; - - for(mBufferPointer = 0; mBufferPointer < samples.length; mBufferPointer += 4) - { - mIAccumulator = 0.0f; - mQAccumulator = 0.0f; - - for(mCoefficientPointer = 0; mCoefficientPointer < mHalf; mCoefficientPointer += 4) - { - //Half band filter coefficients are mirrored, so we add the mirrored samples and then multiply by - //one of the coefficients to achieve the same effect. - mIAccumulator += mCoefficients[mCoefficientPointer] * - (mBuffer[mBufferPointer + mCoefficientPointer] + - mBuffer[mBufferPointer + (mCoefficientsLengthMinus2 - mCoefficientPointer)]); - - mQAccumulator += mCoefficients[mCoefficientPointer] * - (mBuffer[mBufferPointer + mCoefficientPointer + 1] + - mBuffer[mBufferPointer + (mCoefficientsLengthMinus2 - mCoefficientPointer) + 1]); - } - - mIAccumulator += mBuffer[mBufferPointer + mHalf] * CENTER_COEFFICIENT; - mQAccumulator += mBuffer[mBufferPointer + mHalf + 1] * CENTER_COEFFICIENT; - - filtered[mBufferPointer / 2] = mIAccumulator; - filtered[mBufferPointer / 2 + 1] = mQAccumulator; - } - - return filtered; - } - - /** - * Decimates the complex samples and returns a buffer of decimated samples. - * @param buffer to decimate - * @return decimated buffer. - */ - @Override - public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer) - { - float[] decimated = decimateComplex(buffer.getSamples()); - buffer.decrementUserCount(); - return mReusableComplexBufferQueue.getBuffer(decimated, buffer.getTimestamp()); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java deleted file mode 100644 index b86f190f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/complex/ComplexHalfBandFilter.java +++ /dev/null @@ -1,111 +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 - * **************************************************************************** - */ -package io.github.dsheirer.dsp.filter.halfband.complex; - -import io.github.dsheirer.dsp.filter.Filters; -import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2; -import io.github.dsheirer.dsp.mixer.Oscillator; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -public class ComplexHalfBandFilter -{ - private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter"); - - private HalfBandFilter2 mIFilter; - private HalfBandFilter2 mQFilter; - private float mResidualISample; - private float mResidualQSample; - private boolean mHasResidual; - private int mOutputBufferLength; - private float[] mSamples; - private int mSamplesPointer; - private float[] mFilteredSamples; - private int mFilteredSamplesPointer; - - public ComplexHalfBandFilter(float[] coefficients) - { - mIFilter = new HalfBandFilter2(coefficients); - mQFilter = new HalfBandFilter2(coefficients); - } - - public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer) - { - mSamples = originalBuffer.getSamples(); - mSamplesPointer = 0; - - mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2; - - ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength); - mFilteredSamples = filteredBuffer.getSamples(); - mFilteredSamplesPointer = 0; - - if(mHasResidual) - { - mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]); - mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]); - mHasResidual = false; - } - - while(mSamplesPointer + 3 < mSamples.length) - { - mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]); - mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]); - mSamplesPointer += 4; - } - - if(mSamplesPointer < mSamples.length) - { - mHasResidual = true; - mResidualISample = mSamples[mSamplesPointer++]; - mResidualQSample = mSamples[mSamplesPointer]; - } - - originalBuffer.decrementUserCount(); - return filteredBuffer; - } - - public static void main(String[] args) - { - Oscillator oscillator = new Oscillator(1, 16); - ComplexHalfBandFilter filter = new ComplexHalfBandFilter(Filters.HALF_BAND_FILTER_27T.getCoefficients()); - - ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test"); - - ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8); - oscillator.generateComplex(complexBuffer); - ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer); - filteredBuffer.decrementUserCount(); - - ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10); - oscillator.generateComplex(complexBuffer2); - ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2); - filteredBuffer2.decrementUserCount(); - - ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8); - oscillator.generateComplex(complexBuffer3); - ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3); - filteredBuffer3.decrementUserCount(); - - - - - - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java deleted file mode 100644 index 6b1bf893..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * 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 - * ***************************************************************************** - */ -package io.github.dsheirer.dsp.filter.halfband.real; - -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import org.apache.commons.lang3.Validate; - -public class HalfBandFilter -{ - private 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 ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("HalfBandFilter"); - - /** - * Half-Band filter for processing real float samples. - * - * 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 HalfBandFilter(float[] coefficients, float gain) - { - //TODO: update this to use SIMD structures with 2 float arrays and a toggle operation - - - mCoefficients = coefficients; - mGain = gain; - - mBufferSize = mCoefficients.length; - mBuffer = new float[mBufferSize]; - - generateIndexMap(mCoefficients.length); - } - - public HalfBandFilter(float[] coefficients, float gain, boolean decimate) - { - this(coefficients, gain); - - throw new IllegalStateException("This filter must be updated to support non-decimate option"); - } - - public void dispose() - { - } - - /** - * Inserts the sample into the buffer without calculating a filtered value - */ - public void insert(float sample) - { - mBuffer[mBufferPointer] = sample; - - mBufferPointer++; - - mBufferPointer = mBufferPointer % mBufferSize; - } - - public ReusableFloatBuffer filter(ReusableFloatBuffer input) - { - float[] inputSamples = input.getSamples(); - ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length); - float[] outputSamples = output.getSamples(); - - for(int x = 0; x < inputSamples.length; x++) - { - outputSamples[x] = filter(inputSamples[x]); - } - - input.decrementUserCount(); - - return output; - } - - /** - * Calculate the filtered value by applying the coefficients against the samples in mBuffer - */ - public float filter(float sample) - { - insert(sample); - - float accumulator = 0; - - //Start with the center tap value - accumulator += mCoefficients[mCenterCoefficient] * mBuffer[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[mIndexMap[mBufferPointer][x]] + mBuffer[mIndexMap[mBufferPointer][x + 1]]); - } - - return 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; - } - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java b/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java deleted file mode 100644 index ddfd5724..00000000 --- a/src/main/java/io/github/dsheirer/dsp/filter/halfband/real/HalfBandFilter2.java +++ /dev/null @@ -1,132 +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 - * **************************************************************************** - */ -package io.github.dsheirer.dsp.filter.halfband.real; - -import io.github.dsheirer.buffer.FloatCircularBuffer; -import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; - -import java.util.Random; - -/** - * Implements a half-band filter that produces one filtered output for every two input samples. - * - * This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when - * supported by the host CPU. - */ -public class HalfBandFilter2 -{ - private static final float CENTER_COEFFICIENT = 0.5f; - private float[] mCoefficients; - private float[] mEvenSamples; - private float mAccumulator; - private int mPointer; - private FloatCircularBuffer mCircularBuffer; - - /** - * Creates a half band filter with inherent decimation by two. - * - * @param coefficients of the half-band filter that is odd-length where all odd index coefficients are - * zero valued except for the middle odd index coefficient which should be valued 0.5 - */ - public HalfBandFilter2(float[] coefficients) - { - if((coefficients.length + 1) % 4 != 0) - { - throw new IllegalArgumentException("Half-band filter coefficients must be odd-length and symmetrical (length = [x * 4 + 1]"); - } - - int half = coefficients.length / 2 + 1; - - mCircularBuffer = new FloatCircularBuffer(half / 2); - mCoefficients = new float[half]; - mEvenSamples = new float[half]; - - //Use only the even coefficients since the odd coefficients are all zero-valued - for(int x = 0; x < coefficients.length; x += 2) - { - mCoefficients[x / 2] = coefficients[x]; - } - } - - public float filter(float sample1, float sample2) - { - //Structured for SIMD array copy optimization when supported by host CPU - System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1); - - mEvenSamples[0] = sample1; - - mAccumulator = 0.0f; - - //Structured for SIMD dot.product optimization when supported by host CPU - for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++) - { - mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]); - } - - mAccumulator += (mCircularBuffer.getAndPut(sample2) * CENTER_COEFFICIENT); - - return mAccumulator; - } - - public float[] filter(float[] samples) - { - float[] filtered = new float[samples.length / 2]; - for(int x = 0; x < samples.length; x += 2) - { - filtered[x / 2] = filter(samples[x], samples[x + 1]); - } - - return filtered; - } - - public static void main(String[] args) - { - System.out.println("Starting ..."); - float[] taps = FilterFactory.getHalfBand(99, Window.WindowType.HAMMING); - - HalfBandFilter2 halfband = new HalfBandFilter2(taps); - - Random random = new Random(); - float[] samples = new float[1024]; - float[] filtered = new float[512]; - - for(int x = 0; x < samples.length; x++) - { - samples[x] = (2.0f * random.nextFloat()) - 1.0f; - } - - System.out.println("Testing ..."); - long current = System.currentTimeMillis(); - - int iterations = 10_000_000; - - for(int x = 0; x < iterations; x++) - { - for(int y = 0; y < samples.length; y += 2) - { - filtered[y / 2] = halfband.filter(samples[y], samples[y + 1]); - } - } - - long elapsed = System.currentTimeMillis() - current; - - System.out.println("Elapsed: " + (elapsed / 1000d) + " seconds"); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java index 32de3ee0..aacd5870 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransform.java @@ -1,246 +1,122 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.dsp.filter.hilbert; -import io.github.dsheirer.dsp.filter.Filters; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.Arrays; - -public class HilbertTransform +/** + * Base Hilbert transform class. + */ +public abstract class HilbertTransform { - private static final Logger mLog = LoggerFactory.getLogger(HilbertTransform.class); - - private float[] mHilbertFilter; - private float[] mBuffer; - private int mBufferSize; - private int mBufferPointer; - private int[][] mIndexMap; - private int mMapHeight; - private int mCenterTapIndex; - private float mCenterCoefficent; - - private boolean mInvertFlag = false; - - /** - * Hilbert transform filter used for converting real-valued samples into - * complex valued samples using frequency translation (FS/4) and a half-band - * filter. + /* Half-band filter coefficients retrieved October 2015 from: + * https://github.com/airspy/host/libairspy/src/filters.h * - * This filter uses a circular sample buffer and a pre-calculated - * index map to correctly map the filter coefficients to the circular delay - * buffer contents as each new sample is added to the buffer and processed. + * Copyright (C) 2014, Youssef Touil * - * Half-band filter coefficients used in this filter must be of length N - * where (N + 1) is a multiple of 4. This filter uses a pre-defined half - * band filter with 47 coefficients. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * This transform process is described in Understanding Digital Signal - * Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and - * implemented as described in Section 13.37.1 and 13.37.2 (p 802-804) - */ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + public static final float[] HALF_BAND_FILTER_47_TAP = new float[] + { + -0.000998606272947510f, 0.0f, 0.001695637278417295f, 0.0f, + -0.003054430179754289f, 0.0f, 0.005055504379767936f, 0.0f, + -0.007901319195893647f, 0.0f, 0.011873357051047719f, 0.0f, + -0.017411159379930066f, 0.0f, 0.025304817427568772f, 0.0f, + -0.037225225204559217f, 0.0f, 0.057533286997004301f, 0.0f, + -0.102327462004259350f, 0.0f, 0.317034472508947400f, 0.5f, + 0.317034472508947400f, 0.0f, -0.102327462004259350f, 0.0f, + 0.057533286997004301f, 0.0f, -0.037225225204559217f, 0.0f, + 0.025304817427568772f, 0.0f, -0.017411159379930066f, 0.0f, + 0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f, + 0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f, + 0.001695637278417295f, 0.0f, -0.000998606272947510f + }; + + protected float[] mCoefficients; + protected int mIOverlap = 11; + protected int mQOverlap = 23; + protected float[] mIBuffer = new float[mIOverlap]; + protected float[] mQBuffer = new float[mQOverlap]; + public HilbertTransform() { - convertHalfBandToHilbert(Filters.HALF_BAND_FILTER_47T.getCoefficients()); - - mBufferSize = mHilbertFilter.length + 1; - mBuffer = new float[mBufferSize]; - - mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2]; - - generateIndexMap(mHilbertFilter.length); + mCoefficients = convertHalfBandToHilbert(HALF_BAND_FILTER_47_TAP); } /** - * Filters the continuous real-sample stream by treating the stream as a - * set of interleaved values and applying two hilbert filters against each - * interleaved set (I & Q). Frequency translation has already been - * pre-applied to the hilbert filter coefficients. - * - * Filtering of the inphase component is a simple multiply by the center - * coefficient (0.5) since all of the other coefficients are zero-valued. - * - * Filtering of the quadrature component uses a folded FIR structure and - * expects the hilbert coefficients to be symmetric with all negative valued - * coefficients below the center coefficient and all positive-valued coefficients - * above the center coefficient. So, convolution only applies the filter - * coefficient against the difference of the two samples. - * - * ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 ) - * - * Performs frequency translation by FS/2 on the final filtered values by - * applying a sequence of 1,-1 (sign change) to each I/Q output sample. + * Converts the real sample array to complex samples as half the sample rate + * @param samples to convert + * @return converted samples */ - public float[] filter(float[] samples) - { - float accumulator; - - for(int y = 0; y < samples.length; y += 2) - { - insert(samples[y]); - insert(samples[y + 1]); - - accumulator = 0.0f; - - int index = mBufferPointer / 2; - - for(int x = 0; x < mHilbertFilter.length / 2; x += 2) - { - accumulator += mHilbertFilter[x] * - (mBuffer[mIndexMap[index][x + 1]] - - mBuffer[mIndexMap[index][x]]); - } - - //Perform FS/2 frequency translation on the final filtered values - if(mInvertFlag) - { - //inphase - samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]); - - //quadrature - samples[y + 1] = -accumulator; - } - else - { - //inphase - samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]]; - - //quadrature - samples[y + 1] = accumulator; - } - - mInvertFlag = !mInvertFlag; - } - - return samples; - } + public abstract ComplexSamples filter(float[] samples); /** - * Inserts the sample into the circular buffer, overwriting the oldest value + * Converts the half-band filter coefficients for use as hilbert transform filter coefficients. Sets all + * even-numbered coefficients left of center tap to negative and all even-numbered coefficients right of + * center tap to positive and sets the center tap from 0.5 to 0.0, resulting in an overall 2x reduction + * in gain. We throw away the odd-number coefficients because they are 0.0 or 0.5 (center). + * + * Since we need the filter to be reversed for implementation and since the filter is symmetrical, we + * flip it during assignment and make the left of center positive and the right of center negative. + * + * Note: we apply a 2.0 gain to the coefficients to compensate for the loss, or zero-ing of the original + * 0.5 center coefficient. + * + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, sections 13.1.2 and + * 13.1.3 (p 674-678) and implemented as described in Section 13.37.1 and 13.37.2 (p 802-804) */ - private void insert(float sample) + public static float[] convertHalfBandToHilbert(float[] coefficients) { - mBuffer[mBufferPointer++] = sample; - - mBufferPointer = mBufferPointer % mBufferSize; - } - - /** - * Creates an index map to support efficient lookup of sample indexes from - * the circular buffer. - * - * The first index of the map corresponds to the current buffer pointer - * setting. - * - * The second index is structured in sample pairs to align with - * the hilbert filter coefficients. For example, the first iteration of - * the filter would use filter coefficient 0 and the index map values 0 and - * 1 would be pointing to samples 46 and 0. - * - * The final element of each index set is the index of the center sample. - */ - private void generateIndexMap(int size) - { - mMapHeight = size / 2 + 1; - int mapWidth = mMapHeight + 1; - - mIndexMap = new int[mMapHeight][mapWidth]; - - //Setup the first row with buffer pointer index 0 as a starting point - for(int x = 0; x < mapWidth - 1; x += 2) - { - mIndexMap[0][x] = size - 1 - x; - mIndexMap[0][x + 1] = x; - } - - //Place the center index at the end of the array - mCenterTapIndex = mapWidth - 1; - - mIndexMap[0][mCenterTapIndex] = size / 2; - - //For each subsequent row, increment the previous row's value by 2, - //wrapping as needed, to keep the values between 0 and size - 1 - for(int x = 1; x < mMapHeight; x++) - { - for(int y = 0; y < mapWidth; y++) - { - mIndexMap[x][y] = mIndexMap[x - 1][y] + 2; - - if(mIndexMap[x][y] >= size) - { - mIndexMap[x][y] -= size + 1; - - //Special handling for center tap wrap around - if(y == mCenterTapIndex && mIndexMap[x][y] < 0) - { - mIndexMap[x][y] = size; - } - } - } - } - } - - /** - * Logs the index map contents - */ - public void logIndexMap() - { - for(int[] indexSet : mIndexMap) - { - mLog.debug("Row:" + Arrays.toString(indexSet)); - } - } - - /** - * Converts the half-band filter coefficients for use as hilbert transform - * filter coefficients. Sets all even-numbered coefficients left of center - * to negative and all even-numbered coefficients right of center to positive - * which applies a FS/4 frequency translation to the coefficients. - * - * Note: even though the coefficients above the center coefficient are being - * defined, they are unused during the convolution process since we're using - * a folded FIR structure to exploit the symmetric nature of the half-band - * filter coefficients. The polarity difference between the upper and lower - * halves is accounted for in the filter() method. - * - * Note: we apply a 2.0 gain to the coefficients to compensate for the loss - * of splitting the signal via two filters. - */ - private void convertHalfBandToHilbert(float[] coefficients) - { - mHilbertFilter = new float[coefficients.length]; + float[] hilbert = new float[coefficients.length / 2 + 1]; int middle = coefficients.length / 2; - for(int x = 0; x < coefficients.length; x++) + for(int x = 0; x < coefficients.length; x += 2) { if(x < middle) { - mHilbertFilter[x] = 2.0f * -FastMath.abs(coefficients[x]); + hilbert[x / 2] = -2.0f * FastMath.abs(coefficients[x]); } else if(x > middle) { - mHilbertFilter[x] = 2.0f * FastMath.abs(coefficients[x]); - } - else - { - mHilbertFilter[x] = 2.0f * coefficients[x]; + hilbert[x / 2] = 2.0f * FastMath.abs(coefficients[x]); } } + + return hilbert; } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java new file mode 100644 index 00000000..153464e8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/HilbertTransformFactory.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +/** + * Factory class for creating an optimal implementation of a Hilbert Transform as determined by the + * Calibration manager. + */ +public class HilbertTransformFactory +{ + public static HilbertTransform getHilbertTransform() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.HILBERT_TRANSFORM); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorHilbertTransformDefaultBits(); + case VECTOR_SIMD_64: + return new VectorHilbertTransform64Bits(); + case VECTOR_SIMD_128: + return new VectorHilbertTransform128Bits(); + case VECTOR_SIMD_256: + return new VectorHilbertTransform256Bits(); + case VECTOR_SIMD_512: + return new VectorHilbertTransform512Bits(); + case SCALAR: + case UNCALIBRATED: + default: + return new ScalarHilbertTransform(); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java new file mode 100644 index 00000000..dcaa0660 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/LegacyHilbertTransform.java @@ -0,0 +1,248 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.filter.hilbert; + +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +public class LegacyHilbertTransform +{ + private static final Logger mLog = LoggerFactory.getLogger(LegacyHilbertTransform.class); + + private float[] mHilbertFilter; + private float[] mBuffer; + private int mBufferSize; + private int mBufferPointer; + private int[][] mIndexMap; + private int mMapHeight; + private int mCenterTapIndex; + private float mCenterCoefficent; + + private boolean mInvertFlag = false; + + /** + * Hilbert transform filter used for converting real-valued samples into + * complex valued samples using frequency translation (FS/4) and a half-band + * filter. + * + * This filter uses a circular sample buffer and a pre-calculated + * index map to correctly map the filter coefficients to the circular delay + * buffer contents as each new sample is added to the buffer and processed. + * + * Half-band filter coefficients used in this filter must be of length N + * where (N + 1) is a multiple of 4. This filter uses a pre-defined half + * band filter with 47 coefficients. + * + * This transform process is described in Understanding Digital Signal + * Processing, Lyons, 3e, 2011, sections 13.1.2 and 13.1.3 (p 674-678) and + * implemented as described in Section 13.37.1 and 13.37.2 (p 802-804) + */ + public LegacyHilbertTransform() + { + convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP); + + mBufferSize = mHilbertFilter.length + 1; + mBuffer = new float[mBufferSize]; + + mCenterCoefficent = mHilbertFilter[mHilbertFilter.length / 2]; + + generateIndexMap(mHilbertFilter.length); + } + + /** + * Filters the continuous real-sample stream by treating the stream as a + * set of interleaved values and applying two hilbert filters against each + * interleaved set (I & Q). Frequency translation has already been + * pre-applied to the hilbert filter coefficients. + * + * Filtering of the inphase component is a simple multiply by the center + * coefficient (0.5) since all of the other coefficients are zero-valued. + * + * Filtering of the quadrature component uses a folded FIR structure and + * expects the hilbert coefficients to be symmetric with all negative valued + * coefficients below the center coefficient and all positive-valued coefficients + * above the center coefficient. So, convolution only applies the filter + * coefficient against the difference of the two samples. + * + * ( coefficient * value1 ) + (-coefficient * value2 ) = coefficient * ( value1 - value2 ) + * + * Performs frequency translation by FS/2 on the final filtered values by + * applying a sequence of 1,-1 (sign change) to each I/Q output sample. + */ + public float[] filter(float[] samples) + { + float accumulator; + + for(int y = 0; y < samples.length; y += 2) + { + insert(samples[y]); + insert(samples[y + 1]); + + accumulator = 0.0f; + + int index = mBufferPointer / 2; + + for(int x = 0; x < mHilbertFilter.length / 2; x += 2) + { + accumulator += mHilbertFilter[x] * + (mBuffer[mIndexMap[index][x + 1]] - + mBuffer[mIndexMap[index][x]]); + } + + //Perform FS/2 frequency translation on the final filtered values + if(mInvertFlag) + { + //inphase + samples[y] = -(mBuffer[mIndexMap[index][mCenterTapIndex]]); + + //quadrature + samples[y + 1] = -accumulator; + } + else + { + //inphase + samples[y] = mBuffer[mIndexMap[index][mCenterTapIndex]]; + + //quadrature + samples[y + 1] = accumulator; + } + + mInvertFlag = !mInvertFlag; + } + + return samples; + } + + /** + * Inserts the sample into the circular buffer, overwriting the oldest value + */ + private void insert(float sample) + { + mBuffer[mBufferPointer++] = sample; + + mBufferPointer = mBufferPointer % mBufferSize; + } + + /** + * Creates an index map to support efficient lookup of sample indexes from + * the circular buffer. + * + * The first index of the map corresponds to the current buffer pointer + * setting. + * + * The second index is structured in sample pairs to align with + * the hilbert filter coefficients. For example, the first iteration of + * the filter would use filter coefficient 0 and the index map values 0 and + * 1 would be pointing to samples 46 and 0. + * + * The final element of each index set is the index of the center sample. + */ + private void generateIndexMap(int size) + { + mMapHeight = size / 2 + 1; + int mapWidth = mMapHeight + 1; + + mIndexMap = new int[mMapHeight][mapWidth]; + + //Setup the first row with buffer pointer index 0 as a starting point + for(int x = 0; x < mapWidth - 1; x += 2) + { + mIndexMap[0][x] = size - 1 - x; + mIndexMap[0][x + 1] = x; + } + + //Place the center index at the end of the array + mCenterTapIndex = mapWidth - 1; + + mIndexMap[0][mCenterTapIndex] = size / 2; + + //For each subsequent row, increment the previous row's value by 2, + //wrapping as needed, to keep the values between 0 and size - 1 + for(int x = 1; x < mMapHeight; x++) + { + for(int y = 0; y < mapWidth; y++) + { + mIndexMap[x][y] = mIndexMap[x - 1][y] + 2; + + if(mIndexMap[x][y] >= size) + { + mIndexMap[x][y] -= size + 1; + + //Special handling for center tap wrap around + if(y == mCenterTapIndex && mIndexMap[x][y] < 0) + { + mIndexMap[x][y] = size; + } + } + } + } + } + + /** + * Logs the index map contents + */ + public void logIndexMap() + { + for(int[] indexSet : mIndexMap) + { + mLog.debug("Row:" + Arrays.toString(indexSet)); + } + } + + /** + * Converts the half-band filter coefficients for use as hilbert transform + * filter coefficients. Sets all even-numbered coefficients left of center + * to negative and all even-numbered coefficients right of center to positive + * which applies a FS/4 frequency translation to the coefficients. + * + * Note: even though the coefficients above the center coefficient are being + * defined, they are unused during the convolution process since we're using + * a folded FIR structure to exploit the symmetric nature of the half-band + * filter coefficients. The polarity difference between the upper and lower + * halves is accounted for in the filter() method. + * + * Note: we apply a 2.0 gain to the coefficients to compensate for the loss + * of splitting the signal via two filters. + */ + private void convertHalfBandToHilbert(float[] coefficients) + { + mHilbertFilter = new float[coefficients.length]; + + int middle = coefficients.length / 2; + + for(int x = 0; x < coefficients.length; x++) + { + if(x < middle) + { + mHilbertFilter[x] = 2.0f * -FastMath.abs(coefficients[x]); + } + else if(x > middle) + { + mHilbertFilter[x] = 2.0f * FastMath.abs(coefficients[x]); + } + else + { + mHilbertFilter[x] = 2.0f * coefficients[x]; + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java new file mode 100644 index 00000000..3c66beeb --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/ScalarHilbertTransform.java @@ -0,0 +1,137 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; + +import java.util.Arrays; + +/** + * Implements a Hilbert transform using scalar operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class ScalarHilbertTransform extends HilbertTransform +{ + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + float accumulator; + + for(int x = 0; x < bufferLength; x++) + { + accumulator = 0.0f; + + for(int y = 0; y < mCoefficients.length; y++) + { + accumulator += mCoefficients[y] * mQBuffer[x + y]; + } + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator; + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } + + public static void main(String[] args) + { + int iterations = 1_000_000; + int bufferSize = 2048; + + IRealOscillator realOscillator = OscillatorFactory.getRealOscillator(2, 40); + float[] realSamples = realOscillator.generate(bufferSize); + ScalarHilbertTransform hilbertTransform = new ScalarHilbertTransform(); + VectorHilbertTransformDefaultBits vectorDefault = new VectorHilbertTransformDefaultBits(); + VectorHilbertTransform512Bits vector512 = new VectorHilbertTransform512Bits(); + VectorHilbertTransform256Bits vector256 = new VectorHilbertTransform256Bits(); + VectorHilbertTransform128Bits vector128 = new VectorHilbertTransform128Bits(); + VectorHilbertTransform64Bits vector64 = new VectorHilbertTransform64Bits(); + + System.out.println("Starting ..."); + + float accumulator = 0.0f; + long start = System.currentTimeMillis(); + + boolean validate = true; + + if(validate) + { + ComplexSamples scalar = hilbertTransform.filter(realSamples); + ComplexSamples vector = vector512.filter(realSamples); + + System.out.println("SI: " + Arrays.toString(scalar.i())); + System.out.println("VI: " + Arrays.toString(vector.i())); + + System.out.println("SQ: " + Arrays.toString(scalar.q())); + System.out.println("VQ: " + Arrays.toString(vector.q())); + } + else + { + for(int count = 0; count < iterations; count++) + { +// ComplexSamples filtered = hilbertTransform.filter(realSamples); +// ComplexSamples filtered = vectorDefault.filter(realSamples); + ComplexSamples filtered = vector512.filter(realSamples); +// ComplexSamples filtered = vector256.filter(realSamples); +// ComplexSamples filtered = vector128.filter(realSamples); +// ComplexSamples filtered = vector64.filter(realSamples); + + accumulator += filtered.q()[2]; + } + + System.out.println("Accumulator: " + accumulator); + System.out.println("Elapsed: " + ((System.currentTimeMillis() - start) / 1000.0f) + " seconds"); + } + + System.out.println("Finished!"); + + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java new file mode 100644 index 00000000..3aa34efd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform128Bits.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform128Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filterC = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filterD = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filterE = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filterF = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = filterC.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = filterD.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = filterE.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = filterF.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java new file mode 100644 index 00000000..782a53cd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform256Bits.java @@ -0,0 +1,90 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform256Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filterC = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = filterC.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java new file mode 100644 index 00000000..240a0121 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform512Bits.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +import java.util.Arrays; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform512Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + + public VectorHilbertTransform512Bits() + { + super(); + + //Expand 24-tap filter array to 32 with zero-padding to align with AVX512 lane width. + mCoefficients = Arrays.copyOf(mCoefficients, 32); + //Move the last 8 taps to the end of the array to align for processing + System.arraycopy(mCoefficients, 16, mCoefficients, 24, 8); + for(int x = 16; x < 24; x++) + { + mCoefficients[x] = 0.0f; + } + } + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps - 16 in first vector and 8 taps in second, aligned to the right side + //A: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + //B: - - - - - - - - - 16 17 18 19 20 21 22 23 + //We overlap the samples loading so that we don't overshoot the end of the buffer. Since the + //first 8 filter taps are zero, they don't add anything to the dot.product result. + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java new file mode 100644 index 00000000..e242ae85 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransform64Bits.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransform64Bits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator; + + //There are 24 filter taps + FloatVector filterA = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 0); + FloatVector filterB = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 2); + FloatVector filterC = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 4); + FloatVector filterD = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 6); + FloatVector filterE = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 8); + FloatVector filterF = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 10); + FloatVector filterG = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 12); + FloatVector filterH = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 14); + FloatVector filterI = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 16); + FloatVector filterJ = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 18); + FloatVector filterK = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 20); + FloatVector filterL = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, 22); + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + accumulator = filterA.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator); + accumulator = filterB.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator); + accumulator = filterC.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator); + accumulator = filterD.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator); + accumulator = filterE.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator); + accumulator = filterF.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator); + accumulator = filterG.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator); + accumulator = filterH.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator); + accumulator = filterI.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator); + accumulator = filterJ.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator); + accumulator = filterK.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator); + accumulator = filterL.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator); + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java new file mode 100644 index 00000000..138ecb00 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/hilbert/VectorHilbertTransformDefaultBits.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.hilbert; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Implements a Hilbert transform using vector SIMD operations against a pre-defined 47-tap filter. + * As described in Understanding Digital Signal Processing, Lyons, 3e, 2011, Section 13.37.2 (p 804-805) + */ +public class VectorHilbertTransformDefaultBits extends HilbertTransform +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + /** + * Filters the real samples array into complex samples at half the sample rate. + * @param realSamples to filter + * @return complex samples at half the sample rate + */ + public ComplexSamples filter(float[] realSamples) + { + int bufferLength = realSamples.length / 2; + + //Resize the I and Q delay buffers to incoming buffer length plus overlap, if necessary. + if(mIBuffer.length != (bufferLength + mIOverlap)) + { + float[] iTemp = new float[bufferLength + mIOverlap]; + float[] qTemp = new float[bufferLength + mQOverlap]; + System.arraycopy(mIBuffer, 0, iTemp, 0, mIOverlap); + System.arraycopy(mQBuffer, 0, qTemp, 0, mQOverlap); + mIBuffer = iTemp; + mQBuffer = qTemp; + } + + ComplexSamples deinterleaved = SampleUtils.deinterleave(realSamples); + VectorUtilities.checkComplexArrayLength(deinterleaved.i(), deinterleaved.q(), VECTOR_SPECIES); + + System.arraycopy(deinterleaved.i(), 0, mIBuffer, mIOverlap, deinterleaved.i().length); + System.arraycopy(deinterleaved.q(), 0, mQBuffer, mQOverlap, deinterleaved.q().length); + + float[] i = new float[bufferLength]; + float[] q = new float[bufferLength]; + + FloatVector accumulator, filter, samples; + + for(int x = 0; x < bufferLength; x++) + { + accumulator = FloatVector.zero(VECTOR_SPECIES); + + for(int y = 0; y < mCoefficients.length; y += VECTOR_SPECIES.length()) + { + filter = FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, y); + samples = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + y); + accumulator = filter.fma(samples, accumulator); + } + + i[x] = mIBuffer[x]; //Simple delay assignment + q[x] = accumulator.reduceLanes(VectorOperators.ADD); + } + + //Copy residual from end of delay buffers to beginning for next iteration + System.arraycopy(mIBuffer, mIBuffer.length - mIOverlap, mIBuffer, 0, mIOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - mQOverlap, mQBuffer, 0, mQOverlap); + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java index c6d1ad97..a7557113 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/iir/SinglePoleIirFilter.java @@ -8,23 +8,23 @@ public class SinglePoleIirFilter /** * Decay value, in range: 0 <-> 1.0 */ - private double mAlpha; + private float mAlpha; /** * 1.0 - alpha */ - private double mOneMinusAlpha; + private float mOneMinusAlpha; /** * Current filter output value. */ - private double mOutput; + private float mOutput; /** * Constructs a single pole IIR filter * @param alpha decay value in range (0.0 - 1.0) */ - public SinglePoleIirFilter(double alpha) + public SinglePoleIirFilter(float alpha) { if(alpha < 0.0 || alpha > 1.0) { @@ -32,7 +32,7 @@ public class SinglePoleIirFilter } mAlpha = alpha; - mOneMinusAlpha = 1.0 - alpha; + mOneMinusAlpha = 1.0f - alpha; } /** @@ -40,7 +40,7 @@ public class SinglePoleIirFilter * @param sample to process/filter * @return filtered output */ - public double filter(double sample) + public double filter(float sample) { mOutput = (mOutput * mOneMinusAlpha) + (mAlpha * sample); return mOutput; diff --git a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java index 949ebea7..5a5f1771 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java @@ -1,98 +1,107 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.dsp.filter.resample; import com.laszlosystems.libresample4j.Resampler; import com.laszlosystems.libresample4j.SampleBuffers; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.FloatBuffer; +/** + * Real sample buffer resampler + */ public class RealResampler { protected static final Logger mLog = LoggerFactory.getLogger(RealResampler.class); - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler"); private Resampler mResampler; - private Listener mResampledListener; + private Listener mResampledListener; private BufferManager mBufferManager; private double mResampleFactor; /** - * Resampler for real sample buffers. - * @param inputRate - * @param outputRate + * Constructs an instance. + * @param inputRate sample rate + * @param outputRate sample rate + * @param bufferSize for the input and output float buffers sized large enough to hold at least two of the + * input or output buffer lengths, whichever is larger + * @param outputArrayLength to create uniform length output arrays */ - public RealResampler(double inputRate, double outputRate, int inputBufferSize, int outputBufferSize) + public RealResampler(double inputRate, double outputRate, int bufferSize, int outputArrayLength) { mResampleFactor = outputRate / inputRate; mResampler = new Resampler(true, mResampleFactor, mResampleFactor); - mBufferManager = new BufferManager(inputBufferSize, outputBufferSize); + mBufferManager = new BufferManager(bufferSize, outputArrayLength); } /** * Primary input method to the resampler - * @param reusableFloatBuffer to resample + * @param samples to resample */ - public void resample(ReusableFloatBuffer reusableFloatBuffer) + public void resample(float[] samples) { - mBufferManager.load(reusableFloatBuffer); + mBufferManager.load(samples); mResampler.process(mResampleFactor, mBufferManager, false); } /** - * Registers the listener to receive the resampled buffer output - * @param resampledBufferListener to receive buffers + * Registers the listener to receive the resampled output buffers + * @param listener to receive buffers */ - public void setListener(Listener resampledBufferListener) + public void setListener(Listener listener) { - mResampledListener = resampledBufferListener; + mResampledListener = listener; } /** - * Buffer manager for input and output buffers used during the resampling process. The manager will - * auto-resize the input and output buffers to meet the size of the incoming sample buffers. + * Buffer manager for input and output buffers used during the resampling process. */ public class BufferManager implements SampleBuffers { - private int mOutputBufferSize; + private int mOutputArrayLength; private FloatBuffer mInputBuffer; private FloatBuffer mOutputBuffer; - public BufferManager(int inputBufferSize, int outputBufferSize) + /** + * Constructs an instance + * @param bufferSize for the input and output float buffers + * @param outputArrayLength to create uniform length output sample arrays + */ + public BufferManager(int bufferSize, int outputArrayLength) { - mOutputBufferSize = outputBufferSize; + mOutputArrayLength = outputArrayLength; //The size of the underlying byte buffer in bytes is 4 times the requested input buffer size in floats - mInputBuffer = ByteBuffer.allocate(inputBufferSize * 4).asFloatBuffer(); - mOutputBuffer = ByteBuffer.allocate(inputBufferSize * 4).asFloatBuffer(); + mInputBuffer = ByteBuffer.allocate(bufferSize * 4).asFloatBuffer(); + mOutputBuffer = ByteBuffer.allocate(bufferSize * 4).asFloatBuffer(); } /** - * Queues the buffer sample for resampling + * Queues the buffer samples for resampling */ - public void load(ReusableFloatBuffer reusableFloatBuffer) + public void load(float[] samples) { - mInputBuffer.put(reusableFloatBuffer.getSamples()); - reusableFloatBuffer.decrementUserCount(); + mInputBuffer.put(samples); } @Override @@ -126,21 +135,17 @@ public class RealResampler { mOutputBuffer.put(samples, offset, length); - while(mOutputBuffer.position() > mOutputBufferSize) + while(mOutputBuffer.position() > mOutputArrayLength) { - ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize); + float[] resampled = new float[mOutputArrayLength]; mOutputBuffer.flip(); - mOutputBuffer.get(outputBuffer.getSamples()); + mOutputBuffer.get(resampled); mOutputBuffer.compact(); if(mResampledListener != null) { - mResampledListener.receive(outputBuffer); - } - else - { - outputBuffer.decrementUserCount(); + mResampledListener.receive(resampled); } } } diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java b/src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java new file mode 100644 index 00000000..f57780c3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/FmDemodulatorFactory.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.fm; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory for creating FM demodulators + */ +public class FmDemodulatorFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(FmDemodulatorFactory.class); + + /** + * Creates the optimal FM demodulator using calibration data to select the optimal + * implementation from scalar and vector options. + * @return demodulator instance + */ + public static IFmDemodulator getFmDemodulator() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorFMDemodulator(); + case SCALAR: + return new ScalarFMDemodulator(); + default: + mLog.warn("Unrecognized optimal operation for FM demodulator: " + implementation.name()); + return new ScalarFMDemodulator(); + } + } + + /** + * Creates the optimal Squelching FM demodulator using calibration data to select the optimal + * implementation from scalar and vector options. + * @return demodulator instance + */ + public static ISquelchingFmDemodulator getSquelchingFmDemodulator(float alpha, float threshold, int ramp) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorSquelchingFMDemodulator(alpha, threshold, ramp); + case SCALAR: + return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp); + default: + mLog.warn("Unrecognized optimal operation for Squelching FM demodulator: " + implementation.name()); + return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java new file mode 100644 index 00000000..a1fec0e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/IFmDemodulator.java @@ -0,0 +1,14 @@ +package io.github.dsheirer.dsp.fm; + +/** + * Interface for FM demodulator + */ +public interface IFmDemodulator +{ + /** + * Demodulate the complex sample array and return an array of real audio samples. + * @param samples to demodulate + * @return demodulated samples. + */ + float[] demodulate(float[] i, float[] q); +} diff --git a/src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java new file mode 100644 index 00000000..fb6c9896 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/ISquelchingFmDemodulator.java @@ -0,0 +1,42 @@ +package io.github.dsheirer.dsp.fm; + +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.SourceEvent; + +/** + * Interface for FM demodulator that provides squelch control. + */ +public interface ISquelchingFmDemodulator extends IFmDemodulator +{ + /** + * Indicates if the squelch state has changed. + */ + boolean isSquelchChanged(); + + /** + * Indicates if the squelch state is currently muted + */ + boolean isMuted(); + + /** + * Sets the threshold to use for power squelch + * @param threshold in decibels + */ + void setSquelchThreshold(double threshold); + + /** + * Resets the demodulator + */ + void reset(); + + /** + * Registers the listener to receive notifications of squelch change events from the power squelch. + */ + void setSourceEventListener(Listener listener); + + /** + * Receives a source event + */ + void receive(SourceEvent sourceEvent); +} + diff --git a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/ScalarFMDemodulator.java similarity index 63% rename from src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java rename to src/main/java/io/github/dsheirer/dsp/fm/ScalarFMDemodulator.java index 34421994..8491ec4e 100644 --- a/src/main/java/io/github/dsheirer/dsp/fm/FMDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/fm/ScalarFMDemodulator.java @@ -1,34 +1,31 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.dsp.fm; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.sample.complex.Complex; import org.apache.commons.math3.util.FastMath; /** * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. */ -public class FMDemodulator +public class ScalarFMDemodulator implements IFmDemodulator { - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("FMDemodulator"); private float mPreviousI = 0.0f; private float mPreviousQ = 0.0f; protected float mGain; @@ -36,7 +33,7 @@ public class FMDemodulator /** * Creates an FM demodulator instance with a default gain of 1.0. */ - public FMDemodulator() + public ScalarFMDemodulator() { this(1.0f); } @@ -45,7 +42,7 @@ public class FMDemodulator * Creates an FM demodulator instance and applies the gain value to each demodulated output sample. * @param gain to apply to demodulated samples. */ - public FMDemodulator(float gain) + public ScalarFMDemodulator(float gain) { mGain = gain; } @@ -94,6 +91,50 @@ public class FMDemodulator return (float)(angle * mGain); } + public float[] demodulate(float[] i, float[] q) + { + float[] demodulated = new float[i.length]; + + float demodI, demodQ; + + //Demodulate the first sample + demodI = (i[0] * mPreviousI) - (q[0] * -mPreviousQ); + demodQ = (q[0] * mPreviousI) + (i[0] * -mPreviousQ); + + //Check for divide by zero + if(demodI != 0) + { + demodulated[0] = (float)FastMath.atan(demodQ / demodI); + } + else + { + demodulated[0] = (float)FastMath.atan(demodQ / Float.MIN_VALUE); + } + + //Store last sample to previous for processing with next sample buffer + mPreviousI = i[i.length - 1]; + mPreviousQ = q[q.length - 1]; + + //Demodulate the remainder of the sample array + for(int x = 1; x < i.length; x++) + { + demodI = (i[x] * i[x - 1]) - (q[x] * -q[x - 1]); + demodQ = (q[x] * i[x - 1]) + (i[x] * -q[x - 1]); + + //Check for divide by zero + if(demodI != 0) + { + demodulated[x] = (float)FastMath.atan(demodQ / demodI); + } + else + { + demodulated[x] = (float)FastMath.atan(demodQ / Float.MIN_VALUE); + } + } + + return demodulated; + } + /** * Demodulates the complex samples and returns the demodulated value. * @param previous @@ -123,30 +164,6 @@ public class FMDemodulator return angle; } - /** - * Demodulates the complex baseband sample buffer and returns a demodulated reusable buffer with the user count - * set to 1. The complex baseband buffer's user count is decremented after demodulation. - * - * @param basebandSampleBuffer containing samples to demodulate - * @return demodulated sample buffer. - */ - public ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer) - { - ReusableFloatBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount()); - - float[] basebandSamples = basebandSampleBuffer.getSamples(); - float[] demodulatedSamples = demodulatedBuffer.getSamples(); - - for(int x = 0; x < basebandSamples.length; x += 2) - { - demodulatedSamples[x / 2] = demodulate(basebandSamples[x], basebandSamples[x + 1]); - } - - basebandSampleBuffer.decrementUserCount(); - - return demodulatedBuffer; - } - public void dispose() { //no-op diff --git a/src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java new file mode 100644 index 00000000..6845b757 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/ScalarSquelchingFMDemodulator.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.fm; + +import io.github.dsheirer.dsp.squelch.PowerSquelch; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.SourceEvent; + +/** + * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. + * + * Implements listener of source events to process runtime squelch threshold change request events + * which are forwarded to the power squelch control. + */ +public class ScalarSquelchingFMDemodulator extends ScalarFMDemodulator implements ISquelchingFmDemodulator, Listener +{ + private PowerSquelch mPowerSquelch; + private boolean mSquelchChanged = false; + + /** + * Creates an FM demodulator instance with a default gain of 1.0. + */ + public ScalarSquelchingFMDemodulator(float alpha, float threshold, int ramp) + { + mPowerSquelch = new PowerSquelch(alpha, threshold, ramp); + } + + /** + * Registers the listener to receive notifications of squelch change events from the power squelch. + */ + public void setSourceEventListener(Listener listener) + { + mPowerSquelch.setSourceEventListener(listener); + } + + /** + * Sets the threshold for squelch control + * @param threshold (dB) + */ + public void setSquelchThreshold(double threshold) + { + mPowerSquelch.setSquelchThreshold(threshold); + } + + /** + * Indicates if the squelch state has changed during the processing of buffer(s) + */ + public boolean isSquelchChanged() + { + return mSquelchChanged; + } + + /** + * Sets or resets the squelch changed flag. + */ + private void setSquelchChanged(boolean changed) + { + mSquelchChanged = changed; + } + + /** + * Indicates if the squelch state is currently muted + */ + public boolean isMuted() + { + return mPowerSquelch.isMuted(); + } + + @Override + public void receive(SourceEvent sourceEvent) + { + //Only forward squelch threshold change request events + if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java deleted file mode 100644 index fe586a44..00000000 --- a/src/main/java/io/github/dsheirer/dsp/fm/SquelchingFMDemodulator.java +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,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 - ******************************************************************************/ -package io.github.dsheirer.dsp.fm; - -import io.github.dsheirer.dsp.squelch.PowerSquelch; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.source.SourceEvent; - -/** - * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. - * - * Implements listener of source events to process runtime squelch threshold change request events - * which are forwarded to the power squelch control. - */ -public class SquelchingFMDemodulator extends FMDemodulator implements Listener -{ - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("SquelchingFMDemodulator"); - private PowerSquelch mPowerSquelch; - private boolean mSquelchChanged = false; - - /** - * Creates an FM demodulator instance with a default gain of 1.0. - */ - public SquelchingFMDemodulator(double alpha, double threshold, int ramp) - { - mPowerSquelch = new PowerSquelch(alpha, threshold, ramp); - } - - /** - * Registers the listener to receive notifications of squelch change events from the power squelch. - */ - public void setSourceEventListener(Listener listener) - { - mPowerSquelch.setSourceEventListener(listener); - } - - /** - * Demodulates the complex baseband sample buffer and returns a demodulated reusable buffer with the user count - * set to 1. The complex baseband buffer's user count is decremented after demodulation. - * - * @param basebandSampleBuffer containing samples to demodulate - * @return demodulated sample buffer. - */ - @Override - public ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer) - { - setSquelchChanged(false); - - ReusableFloatBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount()); - - float[] basebandSamples = basebandSampleBuffer.getSamples(); - float[] demodulatedSamples = demodulatedBuffer.getSamples(); - float i,q; - - for(int x = 0; x < basebandSamples.length; x += 2) - { - i = basebandSamples[x]; - q = basebandSamples[x + 1]; - - mPowerSquelch.process(i, q); - - if(mPowerSquelch.isUnmuted() || mPowerSquelch.isDecay()) - { - demodulatedSamples[x / 2] = demodulate(i, q); - } - else - { - demodulatedSamples[x / 2] = 0.0f; - } - - if(mPowerSquelch.isSquelchChanged()) - { - setSquelchChanged(true); - } - } - - basebandSampleBuffer.decrementUserCount(); - - return demodulatedBuffer; - } - - /** - * Sets the threshold for squelch control - * @param threshold (dB) - */ - public void setSquelchThreshold(double threshold) - { - mPowerSquelch.setSquelchThreshold(threshold); - } - - /** - * Indicates if the squelch state has changed during the processing of buffer(s) - */ - public boolean isSquelchChanged() - { - return mSquelchChanged; - } - - /** - * Sets or resets the squelch changed flag. - */ - private void setSquelchChanged(boolean changed) - { - mSquelchChanged = changed; - } - - /** - * Indicates if the squelch state is currently muted - */ - public boolean isMuted() - { - return mPowerSquelch.isMuted(); - } - - @Override - public void receive(SourceEvent sourceEvent) - { - //Only forward squelch threshold change request events - if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD) - { - mPowerSquelch.receive(sourceEvent); - } - else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD) - { - mPowerSquelch.receive(sourceEvent); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java new file mode 100644 index 00000000..0d0a89a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/VectorFMDemodulator.java @@ -0,0 +1,141 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.fm; + +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Random; + +/** + * FM demodulator that uses JDK 17+ SIMD vector intrinsics + */ +public class VectorFMDemodulator implements IFmDemodulator +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator.class); + protected static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + protected static final float ZERO = 0.0f; + private static final int BUFFER_OVERLAP = 1; + + //Initialize buffers to be non-null and resize once the first buffer arrives + private float[] mIBuffer = new float[1]; + private float[] mQBuffer = new float[1]; + + public VectorFMDemodulator() + { + } + + @Override public float[] demodulate(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + if(mIBuffer.length != (i.length + BUFFER_OVERLAP)) + { + mIBuffer = new float[i.length + BUFFER_OVERLAP]; + mQBuffer = new float[q.length + BUFFER_OVERLAP]; + } + + //Copy last sample to beginning of buffer arrays + mIBuffer[0] = mIBuffer[mIBuffer.length - 1]; + mQBuffer[0] = mQBuffer[mQBuffer.length - 1]; + + //Copy new samples to end of buffer arrays + System.arraycopy(i, 0, mIBuffer, 1, i.length); + System.arraycopy(q, 0, mQBuffer, 1, q.length); + + float[] demodulated = new float[i.length]; + + FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ; + + for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length()) + { + previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer); + previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer); + currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1); + currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1); + + demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg())); + demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg())); + + //Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors + demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO)); + + demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN); + demod.intoArray(demodulated, bufferPointer); + } + + return demodulated; + } + + public static void main(String[] args) + { + int iterations = 3_000_000; + int sampleSize = 2048; + + Random random = new Random(); + + float[] iSamples = new float[sampleSize]; + float[] qSamples = new float[sampleSize]; + for(int x = 0; x < iSamples.length; x++) + { + iSamples[x] = random.nextFloat() * 2.0f - 1.0f; + qSamples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + boolean validation = false; + + ScalarFMDemodulator legacy = new ScalarFMDemodulator(); + VectorFMDemodulator vector = new VectorFMDemodulator(); + + if(validation) + { + float[] legacySamples = legacy.demodulate(iSamples, qSamples); + float[] vectorSamples = vector.demodulate(iSamples, qSamples); + System.out.println("LEGACY:" + Arrays.toString(legacySamples)); + System.out.println("VECTOR:" + Arrays.toString(vectorSamples)); + } + else + { + System.out.println("Test Starting ..."); + long start = System.currentTimeMillis(); + + double accumulator = 0.0; + + for(int i = 0; i < iterations; i++) + { + float[] samples = legacy.demodulate(iSamples, qSamples); +// float[] samples = vector.demodulate(iSamples, qSamples); + accumulator += samples[3]; + } + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java b/src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java new file mode 100644 index 00000000..915b7cbd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fm/VectorSquelchingFMDemodulator.java @@ -0,0 +1,229 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.fm; + +import io.github.dsheirer.dsp.squelch.PowerSquelch; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Random; + +/** + * FM Demodulator for demodulating complex samples and producing demodulated floating point samples. + * + * Implements listener of source events to process runtime squelch threshold change request events + * which are forwarded to the power squelch control. + */ +public class VectorSquelchingFMDemodulator extends VectorFMDemodulator + implements ISquelchingFmDemodulator, Listener +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorSquelchingFMDemodulator.class); + private static final float TWO = 2.0f; + private PowerSquelch mPowerSquelch; + private boolean mSquelchChanged = false; + + /** + * Creates an FM demodulator instance with a default gain of 1.0. + */ + public VectorSquelchingFMDemodulator(float alpha, float threshold, int ramp) + { + mPowerSquelch = new PowerSquelch(alpha, threshold, ramp); + } + + public void reset() + { + } + + /** + * Registers the listener to receive notifications of squelch change events from the power squelch. + */ + public void setSourceEventListener(Listener listener) + { + mPowerSquelch.setSourceEventListener(listener); + } + + /** + * Demodulates the complex (I/Q) sample arrays + * @param i inphase samples + * @param q quadrature samples + * @return demodulated real samples + */ + @Override + public float[] demodulate(float[] i, float[] q) + { + setSquelchChanged(false); + + float[] demodulated = super.demodulate(i, q); + float[] magnitude = getMagnitude(i, q); + + for(int x = 0; x < i.length; x++) + { + mPowerSquelch.process(magnitude[x]); + + if(!(mPowerSquelch.isUnmuted() || mPowerSquelch.isDecay())) + { + demodulated[x] = ZERO; + } + + if(mPowerSquelch.isSquelchChanged()) + { + setSquelchChanged(true); + } + } + + return demodulated; + } + + /** + * Calculates the magnitude of the complex I/Q sample arrays using SIMD vector intrinsics. + * @param i sample array + * @param q sample array + * @return magnitude array + */ + public static float[] getMagnitude(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + float[] magnitudes = new float[i.length]; + FloatVector iVector, qVector; + + for(int samplePointer = 0; samplePointer < i.length; samplePointer += VECTOR_SPECIES.length()) + { + iVector = FloatVector.fromArray(VECTOR_SPECIES, i, samplePointer); + qVector = FloatVector.fromArray(VECTOR_SPECIES, q, samplePointer); + iVector.pow(TWO).add(qVector.pow(TWO)).intoArray(magnitudes, samplePointer); + } + + return magnitudes; + } + + /** + * Sets the threshold for squelch control + * @param threshold (dB) + */ + public void setSquelchThreshold(double threshold) + { + mPowerSquelch.setSquelchThreshold(threshold); + } + + /** + * Indicates if the squelch state has changed during the processing of buffer(s) + */ + public boolean isSquelchChanged() + { + return mSquelchChanged; + } + + /** + * Sets or resets the squelch changed flag. + */ + private void setSquelchChanged(boolean changed) + { + mSquelchChanged = changed; + } + + /** + * Indicates if the squelch state is currently muted + */ + public boolean isMuted() + { + return mPowerSquelch.isMuted(); + } + + @Override + public void receive(SourceEvent sourceEvent) + { + //Only forward squelch threshold change request events + if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD) + { + mPowerSquelch.receive(sourceEvent); + } + } + + public static void main(String[] args) + { + int iterations = 3_000_000; + int sampleSize = 2048; + + Random random = new Random(); + + float[] iSamples = new float[sampleSize]; + float[] qSamples = new float[sampleSize]; + for(int x = 0; x < iSamples.length; x++) + { + iSamples[x] = random.nextFloat() * 2.0f - 1.0f; + qSamples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + + float POWER_SQUELCH_ALPHA_DECAY = 0.0004f; + float POWER_SQUELCH_THRESHOLD_DB = -78.0f; + int POWER_SQUELCH_RAMP = 4; + + ScalarFMDemodulator fm = new ScalarFMDemodulator(); + VectorFMDemodulator vectorFM = new VectorFMDemodulator(); + ScalarSquelchingFMDemodulator sqLegacy = new ScalarSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + VectorSquelchingFMDemodulator vectorSq = new VectorSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + + boolean validation = false; + + if(validation) + { + float[] noSqFmSamples = fm.demodulate(iSamples, qSamples); + float[] vectorNoSqFmSamples = vectorFM.demodulate(iSamples, qSamples); + float[] sqFmSamples = sqLegacy.demodulate(iSamples, qSamples); + float[] vectorSqFmSamples = vectorSq.demodulate(iSamples, qSamples); + System.out.println(" NO SQ FM:" + Arrays.toString(noSqFmSamples)); + System.out.println("VECTOR NO SQ FM:" + Arrays.toString(vectorNoSqFmSamples)); + System.out.println(" SQ FM:" + Arrays.toString(sqFmSamples)); + System.out.println(" VECTOR SQ FM:" + Arrays.toString(vectorSqFmSamples)); + } + else + { + System.out.println("Test Starting ..."); + long start = System.currentTimeMillis(); + + double accumulator = 0.0; + + for(int i = 0; i < iterations; i++) + { +// float[] samples = sqLegacy.demodulate(iSamples, qSamples); + float[] samples = vectorSq.demodulate(iSamples, qSamples); + accumulator += samples[3]; + } + + double elapsed = System.currentTimeMillis() - start; + + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java index 0d2170a3..ef44f1a6 100644 --- a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java +++ b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java @@ -1,35 +1,32 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.dsp.fsk; import io.github.dsheirer.bits.MessageFramer; +import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.dc.IIRSinglePoleDCRemovalFilter; 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.dsp.symbol.ISyncDetectListener; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +46,7 @@ import org.slf4j.LoggerFactory; * adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However, * the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission. */ -public class LTRDecoder implements Listener, ISyncDetectListener, ISyncStateListener +public class LTRDecoder implements Listener, ISyncDetectListener, ISyncStateListener { private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class); @@ -95,7 +92,7 @@ public class LTRDecoder implements Listener, ISyncDetectLis protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL); protected SynchronizationMonitor mSynchronizationMonitor; private IIRSinglePoleDCRemovalFilter mDCFilter = new IIRSinglePoleDCRemovalFilter(0.99999f); - private RealFIRFilter2 mLowPassFilter = new RealFIRFilter2(sLowPassFilterCoefficients); + private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients); private MessageFramer mMessageFramer; private boolean mSampleDecision; @@ -154,12 +151,12 @@ public class LTRDecoder implements Listener, ISyncDetectLis * @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) { - ReusableFloatBuffer dcFiltered = mDCFilter.filter(buffer); - ReusableFloatBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered); + float[] dcFiltered = mDCFilter.filter(buffer); + float[] lowPassFiltered = mLowPassFilter.filter(dcFiltered); - for(float sample : lowPassFiltered.getSamples()) + for(float sample : lowPassFiltered) { mSampleDecision = sample > 0.0; @@ -178,8 +175,6 @@ public class LTRDecoder implements Listener, ISyncDetectLis mSynchronizationMonitor.increment(); } } - - lowPassFiltered.decrementUserCount(); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java index ff5269c1..ac3d81d6 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java @@ -1,64 +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 + * **************************************************************************** + */ + package io.github.dsheirer.dsp.gain; import io.github.dsheirer.buffer.DoubleCircularBuffer; import io.github.dsheirer.buffer.RealCircularBuffer; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import org.apache.commons.math3.util.FastMath; -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 - 2018 Dennis Sheirer - * Copyright (C) 2011 Alex Csete - * Copyright (C) 2010 Moe Wheatley - * - * 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 - * - * --------------------------------------------------- - * Ported from Alex Csete's gqrx source code at: - * https://github.com/csete/gqrx/blob/74ba2742c89b0dfb66854fc9f72d19e5c6e355b6/dsp/agc_impl.cpp - * - * This Software is released under the "Simplified BSD License" + + + - * Copyright 2010 Moe Wheatley. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are - * those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Moe Wheatley. - ******************************************************************************/ public class AutomaticGainControl { private static final double SAMPLE_RATE = 48000; @@ -135,8 +99,6 @@ public class AutomaticGainControl private RealCircularBuffer mDelayBuffer = new RealCircularBuffer((int)(SAMPLE_RATE * DELAY_TIME_CONSTANT)); private DoubleCircularBuffer mMagnitudeBuffer = new DoubleCircularBuffer((int)(SAMPLE_RATE * WINDOW_TIME_CONSTANT)); - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AutomaticGainControl"); - public AutomaticGainControl() { } @@ -148,20 +110,15 @@ public class AutomaticGainControl * @param input samples to process * @return output samples with gain applied */ - public ReusableFloatBuffer process(ReusableFloatBuffer input) + public float[] process(float[] input) { - ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount()); + float[] output = new float[input.length]; - float[] inputSamples = input.getSamples(); - float[] outputSamples = output.getSamples(); - - for(int x = 0; x < inputSamples.length; x++) + for(int x = 0; x < input.length; x++) { - outputSamples[x] = process(inputSamples[x]); + output[x] = process(input[x]); } - input.decrementUserCount(); - return output; } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java deleted file mode 100644 index 83de223d..00000000 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexAutomaticGainControl.java +++ /dev/null @@ -1,249 +0,0 @@ -package io.github.dsheirer.dsp.gain; - -import io.github.dsheirer.buffer.ComplexCircularBuffer; -import io.github.dsheirer.buffer.DoubleCircularBuffer; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.Provider; -import io.github.dsheirer.sample.complex.Complex; -import org.apache.commons.math3.util.FastMath; - -import java.util.concurrent.atomic.AtomicBoolean; - -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer - * Copyright (C) 2011 Alex Csete - * Copyright (C) 2010 Moe Wheatley - * - * 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 - * - * --------------------------------------------------- - * Ported from Alex Csete's gqrx source code at: - * https://github.com/csete/gqrx/blob/74ba2742c89b0dfb66854fc9f72d19e5c6e355b6/dsp/agc_impl.cpp - * - * This Software is released under the "Simplified BSD License" + + + - * Copyright 2010 Moe Wheatley. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are - * those of the authors and should not be interpreted as representing - * official policies, either expressed or implied, of Moe Wheatley. - ******************************************************************************/ -public class ComplexAutomaticGainControl implements Listener, - Provider -{ - private static final double SAMPLE_RATE = 48000; - - /* Signal delay line - time delay in seconds */ - private static final double DELAY_TIME_CONSTANT = 0.015; - - /* Peak detector window - time delay in seconds */ - private static final double WINDOW_TIME_CONSTANT = 0.018; - - /* Attack time constants in seconds */ - private static final double ATTACK_RISE_TIME_CONSTANT = 0.002; - private static final double ATTACK_FALL_TIME_CONSTANT = 0.005; - - private static final double ATTACK_RISE_ALPHA = 1.0 - - FastMath.exp( -1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT ); - - private static final double ATTACK_FALL_ALPHA = 1.0 - - FastMath.exp( -1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT ); - - /* AGC decay value in milliseconds (20 to 5000) */ - private static final double DECAY = 200; - - /* Ratio between rise and fall times of decay time constants - adjust for - * best action with SSB */ - private static final double DECAY_RISEFALL_RATIO = 0.3; - - private static final double DECAY_RISE_ALPHA = 1.0 - - FastMath.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO ) ); - - private static final double DECAY_FALL_ALPHA = 1.0 - - FastMath.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) ); - - /* Hang timer release decay time constant in seconds */ - @SuppressWarnings( "unused" ) - private static final double RELEASE_TIME_CONSTANT = 0.05; - - /* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */ - private static final double THRESHOLD = -100; - - /* Limit output to about 3db of maximum */ - private static final double AGC_OUT_SCALE = 0.7; - - /* Keep max input and output the same */ - private static final double MAX_AMPLITUDE = 1.0; - private static final double MAX_MANUAL_AMPLITUDE = 1.0; - - /* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */ - private static final double MANUAL_GAIN = 0.0; - - private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE * - FastMath.pow( 10.0, MANUAL_GAIN / 20.0 ); - - /* Specifies dB reduction in output at knee from max output level (0 - 10dB) */ - private static final double SLOPE_FACTOR = 2.0; - - private static final double KNEE = THRESHOLD / 20.0; - - private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0; - - private static final double FIXED_GAIN = AGC_OUT_SCALE * - FastMath.pow( 10.0, KNEE * ( GAIN_SLOPE - 1.0 ) ); - - /* Constant for calc log() so that a value of 0 magnitude = -8 */ - private static final double MIN_CONSTANT = 3.2767E-4; - - private AtomicBoolean mAGCEnabled = new AtomicBoolean( true ); - - private double mPeakMagnitude = 0.0; - private double mAttackAverage = 0.0; - private double mDecayAverage = 0.0; - - private ComplexCircularBuffer mDelayBuffer = - new ComplexCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) ); - private DoubleCircularBuffer mMagnitudeBuffer = - new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) ); - - private Listener mListener; - - public ComplexAutomaticGainControl() - { - } - - @Override - public void receive( Complex currentSample ) - { - Complex delayedSample = mDelayBuffer.get( currentSample ); - - double gain = MANUAL_AGC_GAIN; - - if( mAGCEnabled.get() ) - { - float max = currentSample.maximumAbsolute(); - - double currentMagnitude = FastMath.log10( max + MIN_CONSTANT ) - - FastMath.log10( MAX_AMPLITUDE ); - - double delayedMagnitude = mMagnitudeBuffer.get( currentMagnitude ); - - if( currentMagnitude > mPeakMagnitude ) - { - /* Use current magnitude as peak if it's larger */ - mPeakMagnitude = currentMagnitude; - } - else if( delayedMagnitude == mPeakMagnitude ) - { - /* If delayed magnitude is the current peak, then find a new peak */ - mPeakMagnitude = mMagnitudeBuffer.max(); - } - - /* Exponential decay mode */ - if( mPeakMagnitude > mAttackAverage ) - { - mAttackAverage = ( ( 1.0 - ATTACK_RISE_ALPHA ) * mAttackAverage ) + - ( ATTACK_RISE_ALPHA * mPeakMagnitude ); - } - else - { - mAttackAverage = ( ( 1.0 - ATTACK_FALL_ALPHA ) * mAttackAverage ) + - ( ATTACK_FALL_ALPHA * mPeakMagnitude ); - } - - if( mPeakMagnitude > mDecayAverage ) - { - mDecayAverage = ( ( 1.0 - DECAY_RISE_ALPHA ) * mDecayAverage ) + - ( DECAY_RISE_ALPHA * mPeakMagnitude ); - } - else - { - mDecayAverage = ( ( 1.0 - DECAY_FALL_ALPHA ) * mDecayAverage ) + - ( DECAY_RISE_ALPHA * mPeakMagnitude ); - } - - double magnitude = FastMath.max(mAttackAverage, mDecayAverage); - - if( magnitude < KNEE ) - { - gain = FIXED_GAIN; - } - else - { - gain = AGC_OUT_SCALE * FastMath.pow( 10.0, - magnitude * ( GAIN_SLOPE - 1.0 ) ); - } - - } - - delayedSample.multiply( (float)gain ); - - if( mListener != null ) - { - mListener.receive( delayedSample ); - } - } - - /** - * Enables or disables Automatic Gain Control (AGC). - */ - public void setAGCEnabled( boolean enabled ) - { - mAGCEnabled.set( enabled ); - } - - /** - * Indicates if AGC is enabled - * @return true=AGC, false=MANUAL GAIN - */ - public boolean isAGCEnabled() - { - return mAGCEnabled.get(); - } - - @Override - public void setListener( Listener listener ) - { - mListener = listener; - } - - @Override - public void removeListener( Listener listener ) - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java deleted file mode 100644 index 0eef1a31..00000000 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java +++ /dev/null @@ -1,182 +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 - * **************************************************************************** - */ -package io.github.dsheirer.dsp.gain; - -import io.github.dsheirer.buffer.FloatCircularBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.sample.complex.ComplexSampleListener; - -public class ComplexFeedForwardGainControl implements ComplexSampleListener -{ - public static final float OBJECTIVE_ENVELOPE = 1.0f; - public static final float MINIMUM_ENVELOPE = 0.0001f; - - private ComplexSampleListener mListener; - private FloatCircularBuffer mEnvelopeHistory; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ComplexFeedForwardGainControl"); - - private float mMaxEnvelope = 0.0f; - private float mGain = 1.0f; - - /** - * Dynamic gain control for incoming sample stream to amplify or attenuate - * all samples toward an objective unity)gain, using the maximum envelope - * value detected in the stream history window. - * - * Uses the specified damping factor to limit gain swings. Damping factor - * is applied against the delta between current gain value and a recalculated - * gain value to limit how quickly the gain value will increase or decrease. - * - * @param window - history size to use in detecting maximum envelope value - */ - public ComplexFeedForwardGainControl(int window) - { - mEnvelopeHistory = new FloatCircularBuffer(window); - } - - public void dispose() - { - mListener = null; - } - - @Override - public void receive(float inphase, float quadrature) - { - process(inphase, quadrature); - - /* Apply current gain value to the sample and send to the listener */ - if(mListener != null) - { - mListener.receive(inphase *= mGain, quadrature *= mGain); - } - } - - private void process(float inphase, float quadrature) - { - float envelope = Complex.envelope(inphase, quadrature); - - if(envelope > mMaxEnvelope) - { - mMaxEnvelope = envelope; - - adjustGain(); - } - - /* Replace oldest envelope value with current envelope value */ - float oldestEnvelope = mEnvelopeHistory.getAndPut(envelope); - - /* If the oldest envelope value was the max envelope value, then we - * have to rediscover the max value from the envelope history */ - if(mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope) - { - mMaxEnvelope = MINIMUM_ENVELOPE; - - for(float value : mEnvelopeHistory.getBuffer()) - { - if(value > mMaxEnvelope) - { - mMaxEnvelope = value; - } - } - - adjustGain(); - } - } - - private void adjustGain() - { - mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope; - } - - public void setListener(ComplexSampleListener listener) - { - mListener = listener; - } - - public float[] filter(float[] complesSamples) - { - mMaxEnvelope = MINIMUM_ENVELOPE; - float currentEnvelope; - - for(int x = 0; x < complesSamples.length; x += 2) - { - currentEnvelope = Complex.envelope(complesSamples[x], complesSamples[x + 1]); - - if(currentEnvelope > mMaxEnvelope) - { - mMaxEnvelope = currentEnvelope; - } - } - - adjustGain(); - - float[] processed = new float[complesSamples.length]; - - for(int x = 0; x < complesSamples.length; x += 2) - { - processed[x] = complesSamples[x] * mGain; - processed[x + 1] = complesSamples[x + 1] * mGain; - } - - return processed; - } - - /** - * Applies gain to the complex sample buffer - * @param buffer to apply gain - * @return reusable buffer with the user count incremented to 1 - */ - public ReusableComplexBuffer filter(ReusableComplexBuffer buffer) - { - float[] samples = buffer.getSamples(); - - ReusableComplexBuffer filteredBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - filteredBuffer.setTimestamp(buffer.getTimestamp()); - - float[] filtered = filteredBuffer.getSamples(); - - mMaxEnvelope = MINIMUM_ENVELOPE; - - float currentEnvelope; - - for(int x = 0; x < samples.length; x += 2) - { - currentEnvelope = Complex.envelope(samples[x], samples[x + 1]); - - if(currentEnvelope > mMaxEnvelope) - { - mMaxEnvelope = currentEnvelope; - } - } - - adjustGain(); - - for(int x = 0; x < samples.length; x += 2) - { - filtered[x] = samples[x] * mGain; - filtered[x + 1] = samples[x + 1] * mGain; - } - - buffer.decrementUserCount(); - - return filteredBuffer; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/LegacyComplexGain.java similarity index 86% rename from src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java rename to src/main/java/io/github/dsheirer/dsp/gain/LegacyComplexGain.java index 70dcdd0e..162e87bc 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexGain.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/LegacyComplexGain.java @@ -3,12 +3,12 @@ package io.github.dsheirer.dsp.gain; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.Complex; -public class ComplexGain implements Listener +public class LegacyComplexGain implements Listener { private float mGain; private Listener mListener; - public ComplexGain(float gain) + public LegacyComplexGain(float gain) { mGain = gain; } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java new file mode 100644 index 00000000..6f8c0469 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java @@ -0,0 +1,62 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Gain control to apply a fixed level of gain to complex samples. + */ +public abstract class ComplexGain +{ + protected float mGain; + + /** + * Constructs an instance + * @param gain to apply to complex samples + */ + public ComplexGain(float gain) + { + mGain = gain; + } + + /** + * Gain value that will be applied to complex samples + * @return gain value + */ + protected float getGain() + { + return mGain; + } + + /** + * Sets the gain value to apply to the complex samples + * @param gain value + */ + protected void setGain(float gain) + { + mGain = gain; + } + + /** + * Applies a gain value to the complex samples. + * + * Note: the gain is applied directly to the buffer, modifying and returning the + * original sample buffer. + * @param samples to amplify + * @return amplified samples + */ + public ComplexSamples apply(ComplexSamples samples) + { + return apply(samples.i(), samples.q()); + } + + /** + * Applies a gain value to the complex samples. + * + * Note: the gain is applied directly to the buffer, modifying and returning the + * original sample buffer. + * @param i samples to amplify + * @param q samples to amplify + * @return amplified samples + */ + public abstract ComplexSamples apply(float[] i, float q[]); +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java new file mode 100644 index 00000000..afe8a477 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainControl.java @@ -0,0 +1,114 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.SampleUtils; +import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; + +import java.text.DecimalFormat; +import java.util.Arrays; + +public class ComplexGainControl implements IComplexGainControl +{ + public static final float OBJECTIVE_ENVELOPE = 1.0f; + public static final float MINIMUM_ENVELOPE = 0.0001f; + + /** + * Dynamic gain control for incoming sample stream to amplify or attenuate + * all samples toward an objective unity)gain, using the maximum envelope + * value detected in the stream history window. + * + * Uses the specified damping factor to limit gain swings. Damping factor + * is applied against the delta between current gain value and a recalculated + * gain value to limit how quickly the gain value will increase or decrease. + */ + public ComplexGainControl() + { + } + + /** + * Processes the complex I & Q samples and applies gain to achieve an objective unity + * gain for the single sample in the buffer that has the largest envelope. + * @param i samples + * @param q samples + * @return processed/amplified buffer + */ + @Override public ComplexSamples process(float[] i, float[] q) + { + float maxEnvelope = MINIMUM_ENVELOPE; + + for(int x = 0; x < i.length; x++) + { + maxEnvelope = Math.max(maxEnvelope, Complex.envelope(i[x], q[x])); + } + + float gain = OBJECTIVE_ENVELOPE / maxEnvelope; + + float[] iProcessed = new float[i.length]; + float[] qProcessed = new float[q.length]; + + for(int x = 0; x < i.length; x++) + { + iProcessed[x] = i[x] * gain; + qProcessed[x] = q[x] * gain; + } + + return new ComplexSamples(iProcessed, qProcessed); + } + + public static void main(String[] args) + { + int sampleSize = 2048; + ComplexSamples samples = SampleUtils.generateComplexRandomVectorLength(sampleSize); + ComplexGainControl scalar = new ComplexGainControl(); + VectorComplexGainControl vector = new VectorComplexGainControl(); + + boolean validate = false; + + if(validate) + { + ComplexSamples scalarSamples = scalar.process(samples.i(), samples.q()); + ComplexSamples vectorSamples = vector.process(samples.i(), samples.q()); + + System.out.println("SCALAR I: " + Arrays.toString(scalarSamples.i())); + System.out.println("VECTOR I: " + Arrays.toString(vectorSamples.i())); + System.out.println("SCALAR Q: " + Arrays.toString(scalarSamples.q())); + System.out.println("VECTOR Q: " + Arrays.toString(vectorSamples.q())); + } + else + { + double accumulator = 0.0d; + int iterations = 10_000_000; + long start = System.currentTimeMillis(); + + for(int x = 0; x < iterations; x++) + { +// ComplexSamples processed = scalar.process(samples.i(), samples.q()); + ComplexSamples processed = vector.process(samples.i(), samples.q()); + accumulator += processed.i()[3]; + } + + double elapsed = System.currentTimeMillis() - start; + DecimalFormat df = new DecimalFormat("0.000"); + System.out.println("Accumulator: " + accumulator); + System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds"); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java new file mode 100644 index 00000000..0ae9532a --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGainFactory.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +/** + * Factory for selecting and creating the optimal implementation of complex gain + * control for this hardware. + */ +public class ComplexGainFactory +{ + /** + * Instantiates the optimal complex gain control implementation for this hardware, + * as determined by the Calibration Manager. + */ + public static IComplexGainControl getComplexGainControl() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.GAIN_CONTROL_COMPLEX); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + { + return new VectorComplexGainControl(); + } + case SCALAR: + default: + return new ComplexGainControl(); + } + } + + /** + * Instantiates the optimal complex gain implementation for this hardware, + * as determined by the Calibration Manager. + */ + public static ComplexGain getComplexGain(float gain) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.GAIN_CONTROL_COMPLEX); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + { + return new VectorComplexGain(gain); + } + case SCALAR: + default: + return new ScalarComplexGain(gain); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java new file mode 100644 index 00000000..bc2be9eb --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/IComplexGainControl.java @@ -0,0 +1,17 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Interface for complex gain control + */ +public interface IComplexGainControl +{ + /** + * Processes a buffer of complex I & Q samples and applies gain to achieve an objective unity gain. + * @param i samples + * @param q samples + * @return amplified gain samples + */ + ComplexSamples process(float[] i, float[] q); +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java new file mode 100644 index 00000000..d6cb9875 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ScalarComplexGain.java @@ -0,0 +1,35 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +/** + * Scalar implementation of complex gain + */ +public class ScalarComplexGain extends ComplexGain +{ + /** + * Constructs an instance + * @param gain to apply to complex samples + */ + public ScalarComplexGain(float gain) + { + super(gain); + } + + /** + * Applies gain to the complex samples buffer + * @param i samples to amplify + * @param q samples to amplify + * @return amplified samples + */ + @Override public ComplexSamples apply(float[] i, float[] q) + { + for(int x = 0; x < i.length; x++) + { + i[x] *= mGain; + q[x] *= mGain; + } + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java new file mode 100644 index 00000000..899eda23 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGain.java @@ -0,0 +1,42 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector implementation of complex gain that uses JDK 17+ SIMD instructions + */ +public class VectorComplexGain extends ComplexGain +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + /** + * Constructs an instance + * + * @param gain to apply to complex samples + */ + public VectorComplexGain(float gain) + { + super(gain); + } + + /** + * Applies gain to the complex samples buffer + * @param i samples to amplify + * @param q samples to amplify + * @return amplified samples. + */ + @Override public ComplexSamples apply(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + for(int x = 0; x < i.length; x += VECTOR_SPECIES.length()) + { + FloatVector.fromArray(VECTOR_SPECIES, i, x).mul(mGain).intoArray(i, x); + FloatVector.fromArray(VECTOR_SPECIES, q, x).mul(mGain).intoArray(q, x); + } + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java new file mode 100644 index 00000000..2d6d15b5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/VectorComplexGainControl.java @@ -0,0 +1,52 @@ +package io.github.dsheirer.dsp.gain.complex; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Gain control that applies gain to a complex sample buffer to achieve unity gain for + * the single sample that has the largest envelope within the buffer. Uses JDK 17+ SIMD + * vector intrinsics to process the buffer samples. + */ +public class VectorComplexGainControl implements IComplexGainControl +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final float OBJECTIVE_ENVELOPE = 1.0f; + private static final float MINIMUM_ENVELOPE = 0.0001f; + private static final float ENVELOPE_ESTIMATE = 0.4f; + + @Override public ComplexSamples process(float[] i, float[] q) + { + VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES); + + //Determine the largest envelope + FloatVector iAbs, qAbs, maxEnvelope; + + maxEnvelope = FloatVector.zero(VECTOR_SPECIES).add(MINIMUM_ENVELOPE); + + for(int bufferPointer = 0; bufferPointer < i.length; bufferPointer += VECTOR_SPECIES.length()) + { + iAbs = FloatVector.fromArray(VECTOR_SPECIES, i, bufferPointer).abs(); + qAbs = FloatVector.fromArray(VECTOR_SPECIES, q, bufferPointer).abs(); + maxEnvelope = maxEnvelope.max(iAbs.add(qAbs.mul(ENVELOPE_ESTIMATE))); + maxEnvelope = maxEnvelope.max(qAbs.add(iAbs.mul(ENVELOPE_ESTIMATE))); + } + + float gain = OBJECTIVE_ENVELOPE / maxEnvelope.reduceLanes(VectorOperators.MAX); + + float[] iProcessed = new float[i.length]; + float[] qProcessed = new float[q.length]; + + //Apply gain to all I & Q samples + for(int bufferPointer = 0; bufferPointer < i.length; bufferPointer += VECTOR_SPECIES.length()) + { + FloatVector.fromArray(VECTOR_SPECIES, i, bufferPointer).mul(gain).intoArray(iProcessed, bufferPointer); + FloatVector.fromArray(VECTOR_SPECIES, q, bufferPointer).mul(gain).intoArray(qProcessed, bufferPointer); + } + + return new ComplexSamples(iProcessed, qProcessed); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java deleted file mode 100644 index 30594c41..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java +++ /dev/null @@ -1,192 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.sample.complex.Complex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.FloatBuffer; - -public abstract class AbstractOscillator implements IOscillator -{ - private final static Logger mLog = LoggerFactory.getLogger(AbstractOscillator.class); - - private FloatBuffer mSampleBuffer; - private boolean mEnabled; - private double mFrequency; - private double mSampleRate; - - public AbstractOscillator(double frequency, double sampleRate) - { - mFrequency = frequency; - mSampleRate = sampleRate; - mEnabled = frequency != 0; - update(); - } - - /** - * Frequency of the tone being generated by this oscillator - */ - public double getFrequency() - { - return mFrequency; - } - - /** - * Sets or changes the frequency of this oscillator - */ - public void setFrequency(double frequency) - { - mEnabled = frequency != 0; - mFrequency = frequency; - update(); - } - - /** - * Sample rate of this oscillator - */ - public double getSampleRate() - { - return mSampleRate; - } - - /** - * Sets or changes the sample rate of this oscillator - */ - public void setSampleRate(double sampleRate) - { - mSampleRate = sampleRate; - update(); - } - - /** - * Indicates if this oscillator has a non-zero frequency setting. When used as a frequency translating - * mixer, setting a frequency of zero indicates that no frequency correction is needed, so this flag can - * indicate when to apply the oscillator to frequency correct samples. - * - * @return true if the current frequency value is non-zero. - */ - @Override - public boolean isEnabled() - { - return mEnabled; - } - - /** - * Commands sub-class implementations to update internal structures to account for changes in frequency or - * sample rate. The boolean reset indicator optionally commands a reset of the current I/Q values. - */ - protected abstract void update(); - - /** - * Performs complex heterodyne against the samples using this oscillator - * @param samples to mix with this oscillator - */ - @Override - public float[] mixComplex(float[] samples) - { - for(int x = 0; x < samples.length; x += 2) - { - float i = Complex.multiplyInphase(samples[x], samples[x + 1], inphase(), quadrature()); - float q = Complex.multiplyQuadrature(samples[x], samples[x + 1], inphase(), quadrature()); - - samples[x] = i; - samples[x + 1] = q; - - rotate(); - } - - return samples; - } - - /** - * Generates an array of real samples from this oscillator. - * @param sampleCount number of samples to generate and length of the resulting float array. - * @return - */ - @Override - public float[] generateReal(int sampleCount) - { - float[] samples = new float[sampleCount]; - - for(int x = 0; x < sampleCount; x++) - { - rotate(); - samples[x] = quadrature(); - } - - return samples; - } - - /** - * Generates an array of complex samples from this oscillator. - * @param sampleCount number of samples to generate and length of the resulting float array. - * @return - */ - @Override - public float[] generateComplex(int sampleCount) - { - float[] samples = new float[sampleCount * 2]; - - for(int x = 0; x < sampleCount * 2; x += 2) - { - samples[x] = inphase(); - samples[x + 1] = quadrature(); - rotate(); - } - - return samples; - } - - /** - * Generates enough complex samples to fill the reusable complex buffer - * @param reusableComplexBuffer to fill with complex samples - */ - @Override - public void generateComplex(ReusableComplexBuffer reusableComplexBuffer) - { - int sampleCount = reusableComplexBuffer.getSampleCount(); - - if(mSampleBuffer == null || mSampleBuffer.capacity() != sampleCount * 2) - { - mSampleBuffer = FloatBuffer.allocate(sampleCount * 2); - } - - mSampleBuffer.rewind(); - - for(int x = 0; x < sampleCount; x++) - { - mSampleBuffer.put(inphase()); - mSampleBuffer.put(quadrature()); - rotate(); - } - - reusableComplexBuffer.reloadFrom(mSampleBuffer, System.currentTimeMillis()); - } - - /** - * Generates enough real samples to fill the reusable buffer - * @param reusableFloatBuffer to fill with real samples - */ - @Override - public void generateReal(ReusableFloatBuffer reusableFloatBuffer) - { - reusableFloatBuffer.reloadFrom(generateReal(reusableFloatBuffer.getSampleCount()), System.currentTimeMillis()); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java new file mode 100644 index 00000000..3f531d60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixer.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.mixer; + +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.dsp.oscillator.ScalarComplexOscillator; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +/** + * Base complex mixer that wraps an oscillator implementation and mixes complex sample buffers + */ +public abstract class ComplexMixer +{ + private IComplexOscillator mOscillator; + + /** + * Constructs an instance + * @param frequency to generate + * @param sampleRate to generate + * @param disableSIMD to disable use of SIMD vector instructions. + */ + public ComplexMixer(double frequency, double sampleRate, boolean disableSIMD) + { + if(disableSIMD) + { + mOscillator = new ScalarComplexOscillator(frequency, sampleRate); + } + else + { + mOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate); + } + } + + public ComplexMixer(double frequency, double sampleRate) + { + mOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate); + } + + /** + * Sets the frequency of the underlying oscillator + * @param frequency in Hertz + */ + public void setFrequency(double frequency) + { + mOscillator.setFrequency(frequency); + } + + /** + * Indicates if this mixer's oscillator is set to a non-zero frequency value. + */ + public boolean hasFrequency() + { + return mOscillator.hasFrequency(); + } + + /** + * Sets the sample rate of the underlying oscillator + * @param sampleRate in Hertz + */ + public void setSampleRate(double sampleRate) + { + mOscillator.setSampleRate(sampleRate); + } + + /** + * Generates complex samples from the underlying oscillator + * @param sampleCount to generate + * @return complex samples + */ + protected ComplexSamples generate(int sampleCount) + { + return mOscillator.generateComplexSamples(sampleCount); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param samples to mix + * @return mixed samples + */ + public ComplexSamples mix(ComplexSamples samples) + { + return mix(samples.i(), samples.q()); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator + * @param samples interleaved + * @return mixed samples + */ + public ComplexSamples mix(InterleavedComplexSamples samples) + { + return mix(samples.toDeinterleaved()); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param i complex samples to mix + * @param q complex samples to mix + * @return mixed samples + */ + public abstract ComplexSamples mix(float[] i, float[] q); + + +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java new file mode 100644 index 00000000..4610cda4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ComplexMixerFactory.java @@ -0,0 +1,62 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.mixer; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory for creating complex mixer instances from scalar and vector implementations. + */ +public class ComplexMixerFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(ComplexMixerFactory.class); + + /** + * Creates an instance of the optimal implementation of a complex mixer for this hardware. + * @param frequency of the mixing oscillator + * @param sampleRate of the mixing oscillator + * @return optimal instance + */ + public static ComplexMixer getMixer(double frequency, double sampleRate) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.MIXER_COMPLEX); + + switch(implementation) + { + case VECTOR_SIMD_PREFERRED: + { + return new VectorComplexMixer(frequency, sampleRate); + } + case SCALAR: + { + return new ScalarComplexMixer(frequency, sampleRate); + } + default: + { + mLog.warn("Unrecognized complex mixer implementation: " + implementation); + return new ScalarComplexMixer(frequency, sampleRate); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java b/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java deleted file mode 100644 index d1645698..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/FS4DownConverter.java +++ /dev/null @@ -1,69 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; - -public class FS4DownConverter -{ - private int mPointer = 0; - - /** - * Performs complex frequency conversion at one-fourth (-FS/4) of the sample rate using simple value - * assignments. - * - * See: Digital Signal Processing 3e, Lyons, p.674-675 - */ - public FS4DownConverter() - { - } - - public float[] mixComplex(float[] samples) - { - float real; - - for(int x = 0; x < samples.length; x += 2) - { - switch(mPointer) - { - case 0: - //no-op - break; - case 1: - real = samples[x]; - samples[x] = samples[x + 1]; - samples[x + 1] = -real; - break; - case 2: - samples[x] = -samples[x]; - samples[x + 1] = -samples[x + 1]; - break; - case 3: - real = samples[x]; - samples[x] = -samples[x + 1]; - samples[x + 1] = real; - break; - } - - mPointer++; - - if(mPointer >= 4) - { - mPointer = 0; - } - } - - return samples; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java deleted file mode 100644 index 5546bff7..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java +++ /dev/null @@ -1,115 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -/** - * Oscillator interface. - */ -public interface IOscillator -{ - /** - * Frequency of the tone being generated by the oscillator - * @return frequency in hertz - */ - double getFrequency(); - - /** - * Sets the frequency of the tone being generated by the oscillator. - * @param frequency in hertz - */ - void setFrequency(double frequency); - - /** - * Sample rate of the oscillator - * @return sample rate in hertz - */ - double getSampleRate(); - - /** - * Sets the sample rate of the oscillator - * @param sampleRate in hertz - */ - void setSampleRate(double sampleRate); - - - /** - * Indicates if this oscillator is enabled, meaning that it has a non-zero frequency value. - * @return true if the frequency value is non-zero - */ - boolean isEnabled(); - - /** - * Current quadrature/imaginary value of the oscillator. Note: if you are using this value iteratively, make sure - * you invoke rotate() after each time you invoke this method. - * @return current quadrature value of the oscillator. - */ - float quadrature(); - - /** - * Current inphase/real value of the oscillator. Note: if you are using this value iteratively, make sure you - * invoke rotate() after each time you invoke this method. - * @return current inphase value of the oscillator. - */ - float inphase(); - - - /** - * Rotates the oscillator by one sample period to generate new inphase() and quadrature() values. - */ - void rotate(); - - /** - * Generates the specified number of real samples into a sample array. - * @param sampleCount number of samples to generate and length of the resulting float array. - * @return generated samples - */ - float[] generateReal(int sampleCount); - - - /** - * Generates the specified number of complex samples where the samples are arranged as i0,q0,i1,q1 ... iN-1,qN-1 - * @param sampleCount number of samples to generate. - * @return generated complex samples with an array length of 2 * N - */ - float[] generateComplex(int sampleCount); - - - /** - * Generates complex samples and fills the reusable complex buffer using the current system time in millis for - * the update timestamp. - * - * @param reusableComplexBuffer to fill with complex samples - */ - void generateComplex(ReusableComplexBuffer reusableComplexBuffer); - - /** - * Generates real samples and fills the reusable buffer using the current system time in millis for - * the update timestamp. - * - * @param reusableFloatBuffer to fill with complex samples - */ - void generateReal(ReusableFloatBuffer reusableFloatBuffer); - - /** - * Mixes (heterodynes) the complex sample array using the current settings of this oscillator. - * @param complexSamples to mix to a new frequency - * @return mixed/heterdyned samples - */ - float[] mixComplex(float[] complexSamples); -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java deleted file mode 100644 index 9b46665f..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/LowPhaseNoiseOscillator.java +++ /dev/null @@ -1,140 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; - -import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -public class LowPhaseNoiseOscillator extends AbstractOscillator -{ - private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class); - - private static final double TWO_PI = 2.0 * FastMath.PI; - private static final double THREE_HALVES = 3.0 / 2.0; - private double mInphase = 1.0; - private double mQuadrature = 0.0; - private double mPreviousInphase = 1.0; - private double mPreviousQuadrature = 0.0; - private double mCosineAngle; - private double mSineAngle; - private double mGain = 1.0; - - /** - * Ultra-low phase noise complex oscillator as described in Digital Signal Processing 3e, Lyons, p.786 - * - * @param sampleRate for the oscillator - * @param frequency to generate - */ - public LowPhaseNoiseOscillator(double frequency, double sampleRate) - { - super(frequency, sampleRate); - - update(); - } - - @Override - public float quadrature() - { - return (float)mQuadrature; - } - - @Override - public float inphase() - { - return (float)mInphase; - } - - /** - * Updates the internal values after a frequency or sample rate change - */ - protected void update() - { - double anglePerSample = TWO_PI * getFrequency() / getSampleRate(); - - mCosineAngle = FastMath.cos(anglePerSample); - mSineAngle = FastMath.sin(anglePerSample); - } - - /** - * Rotates the oscillator to generate the next complex sample. - */ - public void rotate() - { - mInphase = ((mPreviousInphase * mCosineAngle) - (mPreviousQuadrature * mSineAngle)) * mGain; - mQuadrature = ((mPreviousInphase * mSineAngle) + (mPreviousQuadrature * mCosineAngle)) * mGain; - - mPreviousInphase = mInphase; - mPreviousQuadrature = mQuadrature; - - //Update the gain value for the next rotation - mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature)); - } - - public static long process(AbstractOscillator oscillator, int iterations, int sampleCount) - { - long start = System.nanoTime(); - - for(int x = 0; x < iterations; x++) - { - float[] samples = oscillator.generateComplex(sampleCount); - } - - long duration = System.nanoTime() - start; - - return duration; - } - - public static void main(String[] args) - { - mLog.debug("Starting ..."); - SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS"); - - AbstractOscillator lpno = new LowPhaseNoiseOscillator(100, 800); - AbstractOscillator regular = new Oscillator(100, 800); - - int iterations = 10000; - int sampleCount = 1000000; - - mLog.debug("Warm up - lpno"); - process(lpno, iterations, sampleCount); - process(lpno, iterations, sampleCount); - process(lpno, iterations, sampleCount); - - mLog.debug("Warm up - regular"); - process(regular, iterations, sampleCount); - process(regular, iterations, sampleCount); - process(regular, iterations, sampleCount); - - mLog.debug("Test - lpno"); - process(lpno, iterations, sampleCount); - process(lpno, iterations, sampleCount); - long duration = process(lpno, iterations, sampleCount); - mLog.debug("Duration Low Phase Noise: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration)))); - - mLog.debug("Test - regular"); - process(regular, iterations, sampleCount); - process(regular, iterations, sampleCount); - duration = process(regular, iterations, sampleCount); - mLog.debug("Duration Regular: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration)))); - -// mLog.debug(" Delta: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration2 - duration)))); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java deleted file mode 100644 index 825c5e76..00000000 --- a/src/main/java/io/github/dsheirer/dsp/mixer/Oscillator.java +++ /dev/null @@ -1,69 +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 - * - ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; - -import io.github.dsheirer.sample.complex.Complex; -import org.apache.commons.math3.util.FastMath; - -public class Oscillator extends AbstractOscillator -{ - private Complex mAnglePerSample; - private Complex mCurrentAngle = new Complex(0.0f, -1.0f); - - /** - * Oscillator that produces complex or float samples corresponding to a sine wave oscillating at the specified - * frequency and sample rate - * - * @param frequency - positive or negative frequency in hertz - * @param sampleRate - in hertz - */ - public Oscillator(double frequency, double sampleRate) - { - super(frequency, sampleRate); - } - - /** - * Steps the current angle by the angle per sample amount - */ - @Override - public void rotate() - { - mCurrentAngle.multiply(mAnglePerSample); - mCurrentAngle.fastNormalize(); - } - - @Override - public float inphase() - { - return mCurrentAngle.inphase(); - } - - @Override - public float quadrature() - { - return mCurrentAngle.quadrature(); - } - - /** - * Updates the internal values after a frequency or sample rate change - */ - @Override - protected void update() - { - float anglePerSample = (float)(2.0d * FastMath.PI * getFrequency() / getSampleRate()); - mAnglePerSample = Complex.fromAngle(anglePerSample); - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java new file mode 100644 index 00000000..bd4cdd60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/ScalarComplexMixer.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.mixer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +/** + * Scalar implementation of a complex mixer + */ +public class ScalarComplexMixer extends ComplexMixer +{ + /** + * Constructs an instance + * @param frequency of the mixing oscillator + * @param sampleRate of the mixing oscillator + * @param disableSIMD to disable use of SIMD vector instructions + */ + public ScalarComplexMixer(double frequency, double sampleRate, boolean disableSIMD) + { + super(frequency, sampleRate, disableSIMD); + } + + /** + * Constructs an instance + * @param frequency of the mixing oscillator + * @param sampleRate of the mixing oscillator + */ + public ScalarComplexMixer(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param iSamples complex samples to mix + * @param qSamples complex samples to mix + * @return mixed samples + */ + @Override public ComplexSamples mix(float[] iSamples, float[] qSamples) + { + ComplexSamples mixer = generate(iSamples.length); + + float[] iMixer = mixer.i(); + float[] qMixer = mixer.q(); + + float inphase, quadrature; + + for(int x = 0; x < iSamples.length; x++) + { + inphase = (iMixer[x] * iSamples[x]) - (qMixer[x] * qSamples[x]); + quadrature = (qMixer[x] * iSamples[x]) + (iMixer[x] * qSamples[x]); + iMixer[x] = inphase; + qMixer[x] = quadrature; + } + + return mixer; + } + + /** + * Mixes the interleaved complex samples with oscillator generated samples + * @param samples interleaved + * @return mixed samples + */ + @Override + public ComplexSamples mix(InterleavedComplexSamples samples) + { + ComplexSamples mixer = generate(samples.samples().length / 2); + + float[] iMixer = mixer.i(); + float[] qMixer = mixer.q(); + + float inphase, quadrature; + int offset = 0; + + for(int x = 0; x < iMixer.length; x++) + { + offset = 2 * x; + inphase = (iMixer[x] * samples.samples()[offset]) - (qMixer[x] * samples.samples()[offset + 1]); + quadrature = (qMixer[x] * samples.samples()[offset]) + (iMixer[x] * samples.samples()[offset + 1]); + iMixer[x] = inphase; + qMixer[x] = quadrature; + } + + return mixer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java b/src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java new file mode 100644 index 00000000..b41bae2d --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/mixer/VectorComplexMixer.java @@ -0,0 +1,57 @@ +package io.github.dsheirer.dsp.mixer; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector implementation of a complex mixer that uses JDK 17+ SIMD instructions. + */ +public class VectorComplexMixer extends ComplexMixer +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + /** + * Constructs an instance + * @param frequency of the oscillator + * @param sampleRate of the oscillator + */ + public VectorComplexMixer(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + /** + * Mixes the complex I & Q samples with samples generated from an oscillator. + * @param iSamples complex samples to mix + * @param qSamples complex samples to mix + * @return mixed samples + */ + @Override public ComplexSamples mix(float[] iSamples, float[] qSamples) + { + VectorUtilities.checkComplexArrayLength(iSamples, qSamples, VECTOR_SPECIES); + + //Reuse this complex samples buffer to store the results and return to caller + ComplexSamples mixer = generate(iSamples.length); + + float[] iMixer = mixer.i(); + float[] qMixer = mixer.q(); + + FloatVector iS, qS, iM, qM; + + for(int x = 0; x < iSamples.length; x += VECTOR_SPECIES.length()) + { + iS = FloatVector.fromArray(VECTOR_SPECIES, iSamples, x); + qS = FloatVector.fromArray(VECTOR_SPECIES, qSamples, x); + iM = FloatVector.fromArray(VECTOR_SPECIES, iMixer, x); + qM = FloatVector.fromArray(VECTOR_SPECIES, qMixer, x); + + //Perform complex mixing and store results back to mixer arrays + iM.mul(iS).sub(qM.mul(qS)).intoArray(iMixer, x); + qM.mul(iS).add(iM.mul(qS)).intoArray(qMixer, x); + } + + return mixer; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java new file mode 100644 index 00000000..35e80a71 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/AbstractOscillator.java @@ -0,0 +1,116 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +/** + * Base Oscillator class. + * + * @see OscillatorFactory + */ +public abstract class AbstractOscillator implements IOscillator +{ + protected static final double TWO_PI = Math.PI * 2.0d; + private double mFrequency; + private double mSampleRate; + protected float mAnglePerSample; + protected float mCurrentPhase = 0.0f; + + /** + * Constructs an instance + * @param frequency in hertz + * @param sampleRate in hertz + */ + protected AbstractOscillator(double frequency, double sampleRate) + { + mFrequency = frequency; + mSampleRate = sampleRate; + + update(); + } + + /** + * Angle per sample based on current frequency and sample rate + * @return angle per sample in radians + */ + protected float getAnglePerSample() + { + return mAnglePerSample; + } + + /** + * Updates/calculates the angle per sample from the frequency and sample rate values. + */ + protected void update() + { + mAnglePerSample = (float)(TWO_PI * getFrequency() / getSampleRate()); + } + + /** + * Frequency for this oscillator + * @return frequency in Hertz + */ + public double getFrequency() + { + return mFrequency; + } + + /** + * Sets the frequency for this oscillator + * @param frequency in hertz + */ + public void setFrequency(double frequency) + { + mFrequency = frequency; + update(); + } + + @Override + public boolean hasFrequency() + { + return mFrequency != 0; + } + + /** + * Sample rate for this oscillator + * @return sample rate in Hertz + */ + public double getSampleRate() + { + return mSampleRate; + } + + /** + * Sets the sample rate for this oscillator + * @param sampleRate in hertz + */ + public void setSampleRate(double sampleRate) + { + mSampleRate = sampleRate; + update(); + } + + /** + * Indicates if this oscillator is enabled, meaning it has a frequency that is not equal to zero. + */ + public boolean isEnabled() + { + return mFrequency != 0.0d; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java new file mode 100644 index 00000000..43ab9d1e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4DownConverter.java @@ -0,0 +1,113 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.dsp.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +public class FS4DownConverter +{ + private int mPointer = 0; + + /** + * Performs complex frequency conversion at one-fourth (-FS/4) of the sample rate using simple value + * assignments. + * + * See: Digital Signal Processing 3e, Lyons, p.674-675 + */ + public FS4DownConverter() + { + } + + public float[] mixComplex(float[] samples) + { + float real; + int pointer = mPointer; + + for(int x = 0; x < samples.length; x += 2) + { + switch(pointer) + { + case 0: + //no-op + break; + case 1: + real = samples[x]; + samples[x] = samples[x + 1]; + samples[x + 1] = -real; + break; + case 2: + samples[x] = -samples[x]; + samples[x + 1] = -samples[x + 1]; + break; + case 3: + real = samples[x]; + samples[x] = -samples[x + 1]; + samples[x + 1] = real; + break; + } + + pointer++; + pointer %= 4; + } + + mPointer = pointer; + + return samples; + } + + public ComplexSamples mixComplex(ComplexSamples samples) + { + float temp; + int pointer = mPointer; + + float[] i = samples.i(); + float[] q = samples.q();; + + for(int x = 0; x < i.length; x++) + { + switch(pointer) + { + case 0: + //no-op + break; + case 1: + temp = i[x]; + i[x] = q[x]; + q[x] = -temp; + break; + case 2: + i[x] = -i[x]; + q[x] = -q[x]; + break; + case 3: + temp = i[x]; + i[x] = -q[x]; + q[x] = temp; + break; + } + + pointer++; + pointer %= 4; + } + + mPointer = pointer; + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/FS4UpConverter.java b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4UpConverter.java similarity index 98% rename from src/main/java/io/github/dsheirer/dsp/mixer/FS4UpConverter.java rename to src/main/java/io/github/dsheirer/dsp/oscillator/FS4UpConverter.java index c57abaaa..93b2d657 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/FS4UpConverter.java +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/FS4UpConverter.java @@ -13,7 +13,7 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.dsp.mixer; +package io.github.dsheirer.dsp.oscillator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java new file mode 100644 index 00000000..9b79e190 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/IComplexOscillator.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; + +public interface IComplexOscillator extends IOscillator +{ + /** + * Generates the specified number of complex samples into an interleaved sample array. + * @param sampleCount number of complex samples to generate. + * @return generated samples where each sample is interleaved: I0, Q0, I1, Q1 ... + */ + float[] generate(int sampleCount); + + /** + * Generates the specified number of complex samples into a complex samples object. + * @param sampleCount number of complex samples to generate. + * @return generated samples + */ + ComplexSamples generateComplexSamples(int sampleCount); +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java new file mode 100644 index 00000000..3bfd0ab1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/IOscillator.java @@ -0,0 +1,59 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +public interface IOscillator +{ + /** + * Frequency of the tone being generated by the oscillator + * @return frequency in hertz + */ + double getFrequency(); + + /** + * Sets the frequency of the tone being generated by the oscillator. + * @param frequency in hertz + */ + void setFrequency(double frequency); + + /** + * Indicates if the current frequency value is non-zero. + */ + boolean hasFrequency(); + + /** + * Sample rate of the oscillator + * @return sample rate in hertz + */ + double getSampleRate(); + + /** + * Sets the sample rate of the oscillator + * @param sampleRate in hertz + */ + void setSampleRate(double sampleRate); + + + /** + * Indicates if this oscillator is enabled, meaning that it has a non-zero frequency value. + * @return true if the frequency value is non-zero + */ + boolean isEnabled(); +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java new file mode 100644 index 00000000..4046e894 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/IRealOscillator.java @@ -0,0 +1,30 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +public interface IRealOscillator extends IOscillator +{ + /** + * Generates the specified number of real samples into a sample array. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + float[] generate(int sampleCount); +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java b/src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java new file mode 100644 index 00000000..3aafea0c --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/OscillatorFactory.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory class for creating real and complex oscillators. + * + * Uses the CalibrationManager to determine the optimal (scalar vs vector) oscillator implementation type. + */ +public class OscillatorFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(OscillatorFactory.class); + + /** + * Constructs an optimal implementation of a real sample oscillator using calibration data when available. + * @param frequency of the oscillator + * @param sampleRate of the oscillator + * @return constructed oscillator + */ + public static IRealOscillator getRealOscillator(double frequency, double sampleRate) + { + Implementation operation = CalibrationManager.getInstance().getImplementation(CalibrationType.OSCILLATOR_REAL); + + switch(operation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorRealOscillator(frequency, sampleRate); + case SCALAR: + default: + return new ScalarRealOscillator(frequency, sampleRate); + } + } + + /** + * Constructs an optimal implementation of a complex sample oscillator using calibration data when available. + * @param frequency of the oscillator + * @param sampleRate of the oscillator + * @return constructed oscillator + */ + public static IComplexOscillator getComplexOscillator(double frequency, double sampleRate) + { + Implementation operation = CalibrationManager.getInstance().getImplementation(CalibrationType.OSCILLATOR_COMPLEX); + + switch(operation) + { + case VECTOR_SIMD_PREFERRED: + return new VectorComplexOscillator(frequency, sampleRate); + case SCALAR: + default: + return new ScalarComplexOscillator(frequency, sampleRate); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java new file mode 100644 index 00000000..e945ae15 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarComplexOscillator.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import org.apache.commons.math3.util.FastMath; + +/** + * Scalar implementation of a complex oscillator. + */ +public class ScalarComplexOscillator extends AbstractOscillator implements IComplexOscillator +{ + private float mCosineAngle; + private float mSineAngle; + private float mPreviousInphase = 1.0f; + private float mPreviousQuadrature = 0.0f; + + /** + * Constructs an instance + * + * @param frequency in hertz + * @param sampleRate in hertz + */ + public ScalarComplexOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + /** + * Updates the internal values after a frequency or sample rate change + */ + @Override + protected void update() + { + super.update(); + float angle = getAnglePerSample(); + mCosineAngle = (float)FastMath.cos(getAnglePerSample()); + mSineAngle = (float)FastMath.sin(getAnglePerSample()); + } + + @Override + public float[] generate(int sampleCount) + { + float[] samples = new float[sampleCount * 2]; + float cosineAngle = (float)FastMath.cos(getAnglePerSample()); + float sineAngle = (float)FastMath.sin(getAnglePerSample()); + + float previousInphase, previousQuadrature, gain; + + previousInphase = mPreviousInphase; + previousQuadrature = mPreviousQuadrature; + + int gainPointer = 0; + + for(int samplePointer = 0; samplePointer < samples.length; samplePointer += 2) + { + if(++gainPointer % 10 == 0) + { + gainPointer = 0; + gain = (3.0f - ((previousInphase * previousInphase) + (previousQuadrature * previousQuadrature))) / 2.0f; + + samples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)) * gain; + samples[samplePointer + 1] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)) * gain; + } + else + { + samples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)); + samples[samplePointer + 1] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)); + } + + previousInphase = samples[samplePointer]; + previousQuadrature = samples[samplePointer + 1]; + } + + mPreviousInphase = previousInphase; + mPreviousQuadrature = previousQuadrature; + + return samples; + } + + @Override + public ComplexSamples generateComplexSamples(int sampleCount) + { + float[] iSamples = new float[sampleCount]; + float[] qSamples = new float[sampleCount]; + float cosineAngle = (float)FastMath.cos(getAnglePerSample()); + float sineAngle = (float)FastMath.sin(getAnglePerSample()); + + float previousInphase, previousQuadrature, gain; + + previousInphase = mPreviousInphase; + previousQuadrature = mPreviousQuadrature; + + int gainPointer = 0; + + for(int samplePointer = 0; samplePointer < iSamples.length; samplePointer++) + { + if(++gainPointer % 100 == 0) + { + gainPointer = 0; + gain = (3.0f - ((previousInphase * previousInphase) + (previousQuadrature * previousQuadrature))) / 2.0f; + + iSamples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)) * gain; + qSamples[samplePointer] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)) * gain; + } + else + { + iSamples[samplePointer] = ((previousInphase * cosineAngle) - (previousQuadrature * sineAngle)); + qSamples[samplePointer] = ((previousInphase * sineAngle) + (previousQuadrature * cosineAngle)); + } + + previousInphase = iSamples[samplePointer]; + previousQuadrature = qSamples[samplePointer]; + } + + mPreviousInphase = previousInphase; + mPreviousQuadrature = previousQuadrature; + + return new ComplexSamples(iSamples, qSamples); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java new file mode 100644 index 00000000..faf0a00f --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/ScalarRealOscillator.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +public class ScalarRealOscillator extends AbstractOscillator implements IRealOscillator +{ + /** + * Constructs an instance + * + * @param frequency in hertz + * @param sampleRate in hertz + */ + public ScalarRealOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + } + + @Override + public float[] generate(int sampleCount) + { + float[] samples = new float[sampleCount]; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer++) + { + mCurrentPhase += mAnglePerSample; + mCurrentPhase %= TWO_PI; + samples[samplePointer] = (float)Math.sin(mCurrentPhase); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java new file mode 100644 index 00000000..3c362dde --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorComplexOscillator.java @@ -0,0 +1,189 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.VectorUtilities; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +/** + * Complex oscillator that uses JDK17 SIMD vector operations to generate complex sample arrays. + * + * Note: this class uses a bank of oscillators that are each rotated synchronously, where the oscillator is similar to + * the ComplexOscillator class, but where each oscillator is offset in phase by one sample more than the previous and + * the entire bank is rotated at the sample phase times the SIMD lane width for each sample generation increment. + */ +public class VectorComplexOscillator extends AbstractOscillator implements IComplexOscillator +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorComplexOscillator.class); + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + private float[] mPreviousInphases; + private float[] mPreviousQuadratures; + private float[] mGainInitials; //Set to 3.0f as the first constant in the gain calculation + + /** + * Constructs an instance + * + * @param frequency in hertz + * @param sampleRate in hertz + */ + public VectorComplexOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + + mGainInitials = new float[VECTOR_SPECIES.length()]; + Arrays.fill(mGainInitials, 3.0f); + } + + @Override + protected void update() + { + super.update(); + + float cosineAngle = (float)FastMath.cos(getAnglePerSample()); + float sineAngle = (float)FastMath.sin(getAnglePerSample()); + + if(mPreviousInphases == null || mPreviousQuadratures == null) + { + mPreviousInphases = new float[VECTOR_SPECIES.length()]; + mPreviousQuadratures = new float[VECTOR_SPECIES.length()]; + + mPreviousInphases[0] = 1.0f; + } + + float gain; + + //Setup the previous sample arrays where each index is offset one sample of rotation from the previous sample. + // We don't touch index 0 so that it can maintain the previous phase offset and all other indices are updated + // relative to index 0. + for(int x = 1; x < VECTOR_SPECIES.length(); x++) + { + gain = (3.0f - ((mPreviousInphases[x - 1] * mPreviousInphases[x - 1]) + + (mPreviousQuadratures[x - 1] * mPreviousQuadratures[x - 1]))) / 2.0f; + mPreviousInphases[x] = ((mPreviousInphases[x - 1] * cosineAngle) - (mPreviousQuadratures[x - 1] * sineAngle)) * gain; + mPreviousQuadratures[x] = ((mPreviousInphases[x - 1] * sineAngle) + (mPreviousQuadratures[x -1] * cosineAngle)) * gain; + } + } + + /** + * Generates complex samples. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + @Override public float[] generate(int sampleCount) + { + if(sampleCount % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Requested sample count [" + sampleCount + + "] must be a power of 2 and a multiple of the SIMD lane width [" + VECTOR_SPECIES.length() + "]"); + } + + float[] samples = new float[sampleCount * 2]; + FloatVector previousInphase = FloatVector.fromArray(VECTOR_SPECIES, mPreviousInphases, 0); + FloatVector previousQuadrature = FloatVector.fromArray(VECTOR_SPECIES, mPreviousQuadratures, 0); + FloatVector gainInitials = FloatVector.fromArray(VECTOR_SPECIES, mGainInitials, 0); + + //Sine and cosine angle per sample, with the rotation angle multiplied by the SIMD lane width + float cosAngle = (float)(FastMath.cos(getAnglePerSample() * VECTOR_SPECIES.length())); + float sinAngle = (float)(FastMath.sin(getAnglePerSample() * VECTOR_SPECIES.length())); + + FloatVector gain, inphase, quadrature; + + int gainCounter = 0; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length()) + { + if(++gainCounter % 10 == 0) + { + gain = gainInitials.sub(previousInphase.pow(2.0f).add(previousQuadrature.pow(2.0f))).div(2.0f); + inphase = previousInphase.mul(cosAngle).sub(previousQuadrature.mul(sinAngle)).mul(gain); + quadrature = previousInphase.mul(sinAngle).add(previousQuadrature.mul(cosAngle)).mul(gain); + } + else + { + inphase = previousInphase.mul(cosAngle).sub(previousQuadrature.mul(sinAngle)); + quadrature = previousInphase.mul(sinAngle).add(previousQuadrature.mul(cosAngle)); + } + + float[] interleaved = VectorUtilities.interleave(inphase, quadrature); + System.arraycopy(interleaved, 0, samples, samplePointer * 2, interleaved.length); + + previousInphase = inphase; + previousQuadrature = quadrature; + } + + previousInphase.intoArray(mPreviousInphases, 0); + previousQuadrature.intoArray(mPreviousQuadratures, 0); + + return samples; + } + + /** + * Generates complex samples. + * @param sampleCount number of samples to generate and length of the resulting float array. + * @return generated samples + */ + @Override public ComplexSamples generateComplexSamples(int sampleCount) + { + if(sampleCount % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Requested sample count [" + sampleCount + + "] must be a power of 2 and a multiple of the SIMD lane width [" + VECTOR_SPECIES.length() + "]"); + } + + float[] iSamples = new float[sampleCount]; + float[] qSamples = new float[sampleCount]; + + FloatVector previousInphase = FloatVector.fromArray(VECTOR_SPECIES, mPreviousInphases, 0); + FloatVector previousQuadrature = FloatVector.fromArray(VECTOR_SPECIES, mPreviousQuadratures, 0); + FloatVector gainInitials = FloatVector.fromArray(VECTOR_SPECIES, mGainInitials, 0); + + //Sine and cosine angle per sample, with the rotation angle multiplied by the SIMD lane width + float cosAngle = (float)(FastMath.cos(getAnglePerSample() * VECTOR_SPECIES.length())); + float sinAngle = (float)(FastMath.sin(getAnglePerSample() * VECTOR_SPECIES.length())); + + FloatVector gain, inphase, quadrature; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length()) + { + gain = gainInitials.sub(previousInphase.pow(2.0f).add(previousQuadrature.pow(2.0f))).div(2.0f); + inphase = previousInphase.mul(cosAngle).sub(previousQuadrature.mul(sinAngle)).mul(gain); + quadrature = previousInphase.mul(sinAngle).add(previousQuadrature.mul(cosAngle)).mul(gain); + + inphase.intoArray(iSamples, samplePointer); + quadrature.intoArray(qSamples, samplePointer); + + previousInphase = inphase; + previousQuadrature = quadrature; + } + + previousInphase.intoArray(mPreviousInphases, 0); + previousQuadrature.intoArray(mPreviousQuadratures, 0); + + return new ComplexSamples(iSamples, qSamples); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java new file mode 100644 index 00000000..f9dd5523 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/oscillator/VectorRealOscillator.java @@ -0,0 +1,63 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.oscillator; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Real oscillator that uses SIMD vector intrinsics from JDK17+ Project Panama. + */ +public class VectorRealOscillator extends AbstractOscillator implements IRealOscillator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private float[] mOffsets; + + public VectorRealOscillator(double frequency, double sampleRate) + { + super(frequency, sampleRate); + + mOffsets = new float[VECTOR_SPECIES.length()]; + for(int x = 0; x < mOffsets.length; x++) + { + mOffsets[x] = x + 1.0f; + } + } + + @Override + public float[] generate(int sampleCount) + { + float[] samples = new float[sampleCount]; + + for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length()) + { + FloatVector generated = FloatVector.fromArray(VECTOR_SPECIES, mOffsets, 0); + generated = generated.mul(mAnglePerSample); + generated = generated.add(mCurrentPhase); + mCurrentPhase = generated.lane(VECTOR_SPECIES.length() - 1); + mCurrentPhase %= TWO_PI; + generated = generated.lanewise(VectorOperators.SIN); + generated.intoArray(samples, samplePointer); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java index 5bec6eb5..21f056f7 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulatorInstrumented.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.dsp.psk; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionDirectedDemodulator @@ -35,7 +32,7 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD private Listener mComplexSymbolListener; private Listener mPLLErrorListener; private Listener mPLLFrequencyListener; - private Listener mFilteredGainAppliedComplexBufferListener; + private Listener mFilteredGainAppliedComplexBufferListener; private double mSampleRate; /** @@ -58,15 +55,14 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { if(mFilteredGainAppliedComplexBufferListener != null) { - reusableComplexBuffer.incrementUserCount(); - mFilteredGainAppliedComplexBufferListener.receive(reusableComplexBuffer); + mFilteredGainAppliedComplexBufferListener.receive(complexSamples); } - super.receive(reusableComplexBuffer); + super.receive(complexSamples); } /** @@ -148,12 +144,4 @@ public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionD { mPLLFrequencyListener = listener; } - - /** - * Regsiters the listener to receive complex sample buffers that have been filtered with automatic gain control applied - */ - public void setFilteredGainAppliedComplexBufferListener(Listener listener) - { - mFilteredGainAppliedComplexBufferListener = listener; - } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java index 6b77c083..9f6df37a 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerDemodulatorInstrumented.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.dsp.psk; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; import org.apache.commons.math3.util.FastMath; public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator @@ -35,7 +32,7 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator private Listener mComplexSymbolListener; private Listener mPLLErrorListener; private Listener mPLLFrequencyListener; - private Listener mFilteredGainAppliedComplexBufferListener; + private Listener mFilteredGainAppliedComplexBufferListener; private double mSampleRate; /** @@ -55,15 +52,14 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { if(mFilteredGainAppliedComplexBufferListener != null) { - reusableComplexBuffer.incrementUserCount(); - mFilteredGainAppliedComplexBufferListener.receive(reusableComplexBuffer); + mFilteredGainAppliedComplexBufferListener.receive(complexSamples); } - super.receive(reusableComplexBuffer); + super.receive(complexSamples); } /** @@ -149,7 +145,7 @@ public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator /** * Registers the listener to receive complex sample buffers that have been filtered with automatic gain control applied */ - public void setFilteredGainAppliedComplexBufferListener(Listener listener) + public void setFilteredGainAppliedComplexBufferListener(Listener listener) { mFilteredGainAppliedComplexBufferListener = listener; } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java index aebc59e6..3acf1662 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/PSKDemodulator.java @@ -1,25 +1,28 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.dsp.psk; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; import io.github.dsheirer.sample.complex.ComplexSampleListener; +import io.github.dsheirer.sample.complex.ComplexSamples; public abstract class PSKDemodulator implements ComplexSampleListener { @@ -77,19 +80,18 @@ public abstract class PSKDemodulator implements ComplexSampleListener } /** - * Processes a (filtered) buffer containing complex samples for decoding - * @param reusableComplexBuffer with complex samples + * Processes a complex sample buffer + * @param samples */ - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - float[] samples = reusableComplexBuffer.getSamples(); + float[] i = samples.i(); + float[] q = samples.q(); - for(int x = 0; x < samples.length; x += 2) + for(int x = 0; x < i.length; x++) { - receive(samples[x], samples[x + 1]); + receive(i[x], q[x]); } - - reusableComplexBuffer.decrementUserCount(); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java b/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java index 3311d031..42aa0bd8 100644 --- a/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java +++ b/src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java @@ -1,8 +1,26 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + package io.github.dsheirer.dsp.squelch; import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +34,7 @@ public class PowerMonitor private int mPowerLevelBroadcastCount = 0; private int mPowerLevelBroadcastThreshold; private Listener mSourceEventListener; - private SinglePoleIirFilter mPowerFilter = new SinglePoleIirFilter(0.1); + private SinglePoleIirFilter mPowerFilter = new SinglePoleIirFilter(0.1f); /** * Constructs an instance @@ -42,7 +60,7 @@ public class PowerMonitor * @param inphase complex sample component * @param quadrature complex sample component */ - public void process(double inphase, double quadrature) + public void process(float inphase, float quadrature) { mPowerLevelBroadcastCount++; @@ -59,20 +77,13 @@ public class PowerMonitor } /** - * Processes a complex baseband sample buffer. Note: this method does not decrement the user count - * on the buffer. - * - * @param buffer to process + * Processes I&Q complex baseband sample buffers. */ - public void process(ReusableComplexBuffer buffer) + public void process(float[] i, float[] q) { - float[] samples = buffer.getSamples(); - int offset; - - for(int x = 0; x < buffer.getSampleCount(); x++) + for(int x = 0; x < i.length; x++) { - offset = x * 2; - process(samples[offset], samples[offset + 1]); + process(i[x], q[x]); } } diff --git a/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java b/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java index 3991a3ff..8a59b6f7 100644 --- a/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java +++ b/src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java @@ -42,7 +42,7 @@ public class PowerSquelch implements Listener * @param ramp count of samples before transition between mute and unmute. Setting this value to zero * causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping. */ - public PowerSquelch(double alpha, double squelchThreshold, int ramp) + public PowerSquelch(float alpha, double squelchThreshold, int ramp) { mFilter = new SinglePoleIirFilter(alpha); setSquelchThreshold(squelchThreshold); @@ -85,9 +85,19 @@ public class PowerSquelch implements Listener * @param inphase complex sample component * @param quadrature complex sample component */ - public void process(double inphase, double quadrature) + public void process(float inphase, float quadrature) { - mPower = mFilter.filter(inphase * inphase + quadrature * quadrature); + process(inphase * inphase + quadrature * quadrature); + } + + /** + * Processes a complex IQ sample and changes squelch state when the signal power is above or below the + * threshold value. + * @param magnitude of a complex sample (inphase * inphase + quadrature * quadrature) + */ + public void process(float magnitude) + { + mPower = mFilter.filter(magnitude); mPowerLevelBroadcastCount++; if(mPowerLevelBroadcastCount % mPowerLevelBroadcastThreshold == 0) diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java b/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java index 7a87c079..493788a0 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java @@ -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,32 +14,30 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.dsp.symbol; import io.github.dsheirer.bits.IBinarySymbolProcessor; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + /** * Assembles reusable byte buffers from an incoming stream of boolean values. */ -public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReusableByteBufferProvider +public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(BinaryToByteBufferAssembler.class); - private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("BinaryToByteBufferAssembler"); - private ReusableByteBuffer mCurrentBuffer; - private int mBufferPointer; + private ByteBuffer mCurrentBuffer; private int mBufferSize; private byte mCurrentByte; private int mBitCount; - private Listener mBufferListener; + private Listener mBufferListener; /** * Constructs an assembler to produce reusable byte buffers of the specified size @@ -64,8 +61,7 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu mBufferListener.receive(mCurrentBuffer); } - mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize); - mBufferPointer = 0; + mCurrentBuffer = ByteBuffer.allocate(mBufferSize); } @Override @@ -82,11 +78,11 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu if(mBitCount >= 8) { - mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte; + mCurrentBuffer.put(mCurrentByte); mCurrentByte = 0; mBitCount = 0; - if(mBufferPointer >= mBufferSize) + if(!mCurrentBuffer.hasRemaining()) { getNextBuffer(); } @@ -98,7 +94,7 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu * Registers the listener to receive fully assembled byte buffers from this assembler. */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mBufferListener = listener; } @@ -107,7 +103,7 @@ public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReu * Removes the listener from receiving buffers from this assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mBufferListener = null; } diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java b/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java index edc34e28..5116e70a 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java @@ -1,41 +1,42 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.dsp.symbol; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + /** * Assembles reusable byte buffers from an incoming stream of Dibits. */ -public class DibitToByteBufferAssembler implements Listener, IReusableByteBufferProvider +public class DibitToByteBufferAssembler implements Listener, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(DibitToByteBufferAssembler.class); - private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("DibitToByteBufferAssembler"); - private ReusableByteBuffer mCurrentBuffer; - private int mBufferPointer; + private ByteBuffer mCurrentBuffer; private int mBufferSize; private byte mCurrentByte; private int mDibitCount; - private Listener mBufferListener; + private Listener mBufferListener; /** * Constructs an assembler to produce reusable byte buffers of the specified size @@ -59,8 +60,7 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt mBufferListener.receive(mCurrentBuffer); } - mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize); - mBufferPointer = 0; + mCurrentBuffer = ByteBuffer.allocate(mBufferSize); } @Override @@ -88,11 +88,11 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt if(mDibitCount >= 4) { - mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte; + mCurrentBuffer.put(mCurrentByte); mCurrentByte = 0x00; mDibitCount = 0; - if(mBufferPointer >= mBufferSize) + if(!mCurrentBuffer.hasRemaining()) { getNextBuffer(); } @@ -103,7 +103,7 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt * Registers the listener to receive fully assembled byte buffers from this assembler. */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mBufferListener = listener; } @@ -112,7 +112,7 @@ public class DibitToByteBufferAssembler implements Listener, IReusableByt * Removes the listener from receiving buffers from this assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mBufferListener = null; } diff --git a/src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java b/src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java new file mode 100644 index 00000000..4b07f0dd --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/ScalarWindow.java @@ -0,0 +1,29 @@ +package io.github.dsheirer.dsp.window; + +/** + * Scalar window implementation. + */ +public class ScalarWindow extends Window +{ + /** + * Constructs an instance. + * + * @param coefficients coefficients + */ + public ScalarWindow(float[] coefficients) + { + super(coefficients); + } + + @Override public float[] apply(float[] samples) + { + validate(samples); + + for(int x = 0; x < mCoefficients.length; x++) + { + samples[x] *= mCoefficients[x]; + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java b/src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java new file mode 100644 index 00000000..b49901b4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/VectorWindow.java @@ -0,0 +1,45 @@ +package io.github.dsheirer.dsp.window; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector SIMD window implementation. + */ +public class VectorWindow extends Window +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + + /** + * Constructs an instance. + * + * @param coefficients coefficients + */ + public VectorWindow(float[] coefficients) + { + super(coefficients); + } + + @Override public float[] apply(float[] samples) + { + validate(samples); + + int x = 0; + + //Applies the coefficients to the samples in multiples of the SIMD lane width + for(; x < VECTOR_SPECIES.loopBound(mCoefficients.length); x += VECTOR_SPECIES.length()) + { + FloatVector.fromArray(VECTOR_SPECIES, samples, x) + .mul(FloatVector.fromArray(VECTOR_SPECIES, mCoefficients, x)) + .intoArray(samples, x); + } + + //Cleanup loop to calculate the final indices that are not a multiple of the SIMD lane width + for(; x < mCoefficients.length; x++) + { + samples[x] *= mCoefficients[x]; + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/Window.java b/src/main/java/io/github/dsheirer/dsp/window/Window.java new file mode 100644 index 00000000..52cf6bfa --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/Window.java @@ -0,0 +1,38 @@ +package io.github.dsheirer.dsp.window; + +/** + * Base window implementation. + */ +public abstract class Window +{ + protected float[] mCoefficients; + + /** + * Constructs an instance. + * @param coefficients coefficients + */ + public Window(float[] coefficients) + { + mCoefficients = coefficients; + } + + /** + * Validates that the window coefficients and samples array lengths are the same. + * @param samples to validate + */ + protected void validate(float[] samples) + { + if(mCoefficients.length != samples.length) + { + throw new IllegalArgumentException("Sample array length [" + samples.length + + "] must match window coefficients length[" + mCoefficients.length + "]"); + } + } + + /** + * Applies this window to the samples + * @param samples to window + * @return windowed samples + */ + public abstract float[] apply(float[] samples); +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java b/src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java new file mode 100644 index 00000000..9a52ec38 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/WindowFactory.java @@ -0,0 +1,442 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.window; + +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +/** + * Window factory and corresponding utility methods. + */ +public class WindowFactory +{ + private final static Logger mLog = LoggerFactory.getLogger(WindowFactory.class); + + private static final float PI = (float)FastMath.PI; + private static final float TWO_PI = (float)FastMath.PI * 2.0f; + private static final float FOUR_PI = (float)FastMath.PI * 4.0f; + private static final float SIX_PI = (float)FastMath.PI * 6.0f; + private static final float EIGHT_PI = (float)FastMath.PI * 8.0f; + + /** + * 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 float[] 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 float[] getRectangular(int length) + { + float[] coefficients = new float[length]; + + Arrays.fill(coefficients, 1.0f); + + return coefficients; + } + + /** + * Creates a cosine window of the specified length. + * + * @param length of the window + * @return cosine window + */ + public static float[] getCosine(int length) + { + float[] coefficients = new float[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] = (float)FastMath.cos( + ((float)x * PI) / ((float)length + 1.0f)); + } + } + else //Odd length + { + int half = (int)length / 2; + + for(int x = -half; x < half + 1; x++) + { + coefficients[x + half] = (float)FastMath.cos(((float)x * PI) / ((float)length + 1.0f)); + } + } + + return coefficients; + } + + /** + * Creates a 3-term Blackman window. + * + * @param length of the window + * @return Blackman window + */ + public static float[] getBlackman(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + + float a0 = 0.426590713672f; + float a1 = 0.496560619089f; + float a2 = 0.0768486672399f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos((TWO_PI * (float)x) / denominator)) + + (a2 * (float)FastMath.cos((FOUR_PI * (float)x) / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Nutall window + * + * @param length of the window + * @return Nutall window + */ + public static float[] getNutall(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + float a0 = 0.355768f; + float a1 = 0.487396f; + float a2 = 0.144232f; + float a3 = 0.012604f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Nutall window + * + * @param length of the window + * @return Blackman-Nutall window + */ + public static float[] getBlackmanNutall(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + float a0 = 0.3635819f; + float a1 = 0.4891775f; + float a2 = 0.1365995f; + float a3 = 0.0106411f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static float[] getBlackmanHarris4(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + float a0 = 0.35875f; + float a1 = 0.48829f; + float a2 = 0.14128f; + float a3 = 0.01168f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 7-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static float[] getBlackmanHarris7(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + + float a0 = 0.27105140069342f; + float a1 = 0.43329793923448f; + float a2 = 0.21812299954311f; + float a3 = 0.06592544638803f; + float a4 = 0.01081174209837f; + float a5 = 0.00077658482522f; + float a6 = 0.00001388721735f; + + + for(int x = 0; x < length; x++) + { + float w = TWO_PI * (float)x / denominator; + + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(w)) + + (a2 * (float)FastMath.cos(2.0 * w)) - + (a3 * (float)FastMath.cos(3.0 * w)) + + (a4 * (float)FastMath.cos(4.0 * w)) - + (a5 * (float)FastMath.cos(5.0 * w)) + + (a6 * (float)FastMath.cos(6.0 * w)); + } + + return coefficients; + } + + /** + * Creates a Hamming window + * + * @param length of the window + * @return Hamming window + */ + public static float[] getHamming(int length) + { + float[] coefficients = new float[length]; + + for(int x = 0; x < length; x++) + { + coefficients[x] = 0.54f - (0.46f * (float)FastMath.cos((TWO_PI * x) / (length - 1))); + } + + return coefficients; + } + + /** + * Creates a Hann window + * + * @param length of the window + * @return Hann window + */ + public static float[] getHann(int length) + { + float[] coefficients = new float[length]; + + for(int x = 0; x < length; x++) + { + coefficients[x] = 0.5f - (0.5f * (float)FastMath.cos(TWO_PI * (float)x / (length - 1))); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static float[] getFlatTop(int length) + { + float[] coefficients = new float[length]; + + float denominator = length - 1; + + float a0 = 0.215578948f; + float a1 = 0.41663158f; + float a2 = 0.277263158f; + float a3 = 0.083578947f; + float a4 = 0.006947368f; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * (float)FastMath.cos(TWO_PI * (float)x / denominator)) + + (a2 * (float)FastMath.cos(FOUR_PI * (float)x / denominator)) - + (a3 * (float)FastMath.cos(SIX_PI * (float)x / denominator)) + + (a4 * (float)FastMath.cos(EIGHT_PI * (float)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 float getKaiserBeta(float attenuation) + { + if(attenuation > 50.0) + { + return 0.1102f * (attenuation - 8.7f); + } + else if(attenuation >= 21.0) + { + return (0.5842f * (float)FastMath.pow(attenuation - 21.0, 0.4)) + (0.07886f * (attenuation - 21.0f)); + } + else + { + return 0.0f; + } + } + + /** + * Creates a Kaiser window. + * + * @param length of the window + * @param attenuation desired + * @return window + */ + public static float[] getKaiser(int length, float attenuation) + { + float[] coefficients = new float[length]; + + float beta = getKaiserBeta(attenuation); + + float betaBesselZerothOrder = getBesselZerothOrder(beta); + + float temp; + + for(int x = 0; x < coefficients.length; x++) + { + temp = beta * (float)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 float getBesselZerothOrder(final float x) + { + float f = 1; + final float x2 = x * x * 0.25f; + float xc = x2; + float v = 1 + x2; + for (int i = 2; i < 100; i++) + { + f *= i; + xc *= x2; + final float 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(float[] coefficients, float[] samples) + { + for(int x = 0; x < coefficients.length; x++) + { + samples[x] = 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) + { + float[] coefficients = getWindow(type, samples.length); + + return apply(coefficients, samples); + } + + public static void main(String[] args) + { + float[] kaiser = getKaiser(16, 80.0f); + + mLog.debug("Window:" + Arrays.toString(kaiser)); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/window/WindowType.java b/src/main/java/io/github/dsheirer/dsp/window/WindowType.java new file mode 100644 index 00000000..e0fa57c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/window/WindowType.java @@ -0,0 +1,38 @@ +package io.github.dsheirer.dsp.window; + +import java.util.EnumSet; + +/** + * 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 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); +} diff --git a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java index 5a30881b..6a4fa435 100644 --- a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java +++ b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder.java @@ -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,17 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.edac.trellis; import org.apache.commons.math3.util.FastMath; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - public abstract class ViterbiDecoder { private int mInputBitLength; @@ -129,9 +124,8 @@ public abstract class ViterbiDecoder */ public Path decode(int[] transmittedOutputValues) { - Collection survivingPaths = new ArrayList<>(); - - survivingPaths.add(new Path(createStartingNode())); + Path[] survivingPaths = new Path[getInputValueCount()]; + survivingPaths[0] = new Path(createStartingNode()); //Add all but the last transmitted value to the starting path, creating a set of survivors at each iteration. for(int x = 0; x < transmittedOutputValues.length - 1; x++) @@ -149,7 +143,7 @@ public abstract class ViterbiDecoder * returned that represents each path that terminates at a distinct input/state value that has the least error * value. * - * @param survivingPaths from the previous time instant to evaluate for the next time instant in the trellis + * @param previousPaths from the previous time instant to evaluate for the next time instant in the trellis * using the transmitted output value. * * @param transmittedOutputValue that will be decoded for this time instant. @@ -158,9 +152,9 @@ public abstract class ViterbiDecoder * output value * */ - public Collection add(Collection survivingPaths, int transmittedOutputValue) + public Path[] add(Path[] previousPaths, int transmittedOutputValue) { - Map survivingPathMap = new HashMap<>(); + Path[] survivorPaths = new Path[getInputValueCount()]; for(int x = 0; x < getInputValueCount(); x++) { @@ -168,31 +162,34 @@ public abstract class ViterbiDecoder Node node = createNode(x, transmittedOutputValue); //Evaluate each surviving path by creating a copy and adding the node to it and see if it survives - for(Path path: survivingPaths) + for(Path path: previousPaths) { - Path pathToEvaluate = path.copyOf(); - - pathToEvaluate.add(node); - - if(survivingPathMap.containsKey(node.getInputValue())) + if(path != null) { - Path survivingPath = survivingPathMap.get(node.getInputValue()); + Path pathToEvaluate = path.copyOf(); - //Replace the current survivor path if this evaluate path has a lower error value - if(pathToEvaluate.getError() < survivingPath.getError()) + pathToEvaluate.add(node); + + if(survivorPaths[node.getInputValue()] != null) { - survivingPathMap.put(node.getInputValue(), pathToEvaluate); + Path survivingPath = survivorPaths[node.getInputValue()]; + + //Replace the current survivor path if this evaluate path has a lower error value + if(pathToEvaluate.getError() < survivingPath.getError()) + { + survivorPaths[node.getInputValue()] = pathToEvaluate; + } + } + else + { + //No survivor yet -- set this evaluate path as the first survivor + survivorPaths[node.getInputValue()] = pathToEvaluate; } - } - else - { - //No survivor yet -- set this evaluate path as the first survivor - survivingPathMap.put(node.getInputValue(), pathToEvaluate); } } } - return survivingPathMap.values(); + return survivorPaths; } /** @@ -207,7 +204,7 @@ public abstract class ViterbiDecoder * @return the single survivor path that remains after adding the flushing node to each of the candidate argument * survivor paths. */ - public Path flush(Collection survivingPaths, int transmittedOutputValue) + public Path flush(Path[] survivingPaths, int transmittedOutputValue) { Node flushingNode = createFlushingNode(transmittedOutputValue); @@ -215,11 +212,14 @@ public abstract class ViterbiDecoder for(Path survivingPath: survivingPaths) { - survivingPath.add(flushingNode); - - if(bestPath == null || (survivingPath.getError() < bestPath.getError())) + if(survivingPath != null) { - bestPath = survivingPath; + survivingPath.add(flushingNode); + + if(bestPath == null || (survivingPath.getError() < bestPath.getError())) + { + bestPath = survivingPath; + } } } diff --git a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java index accf5a29..f7529795 100644 --- a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java +++ b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui; @@ -37,6 +34,7 @@ import io.github.dsheirer.gui.playlist.channelMap.ViewChannelMapEditorRequest; import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.UserPreferencesEditor; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; +import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.jmbe.JmbeEditor; import io.github.dsheirer.jmbe.JmbeEditorRequest; @@ -68,6 +66,7 @@ public class JavaFxWindowManager extends Application public static final String ICON_MANAGER = "iconmanager"; public static final String PLAYLIST_EDITOR = "playlist"; public static final String USER_PREFERENCES_EDITOR = "preferences"; + public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog"; public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map"; public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager"; public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor"; @@ -187,6 +186,12 @@ public class JavaFxWindowManager extends Application Platform.exit(); } + public CalibrationDialog getCalibrationDialog(UserPreferences userPreferences) + { + createJFXPanel(); + return new CalibrationDialog(userPreferences); + } + public Stage getIconManagerStage() { if(mIconManagerStage == null) diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index b2691b74..3e499d0b 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -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 @@ -33,7 +33,10 @@ import io.github.dsheirer.controller.channel.ChannelSelectionManager; import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.gui.icon.ViewIconManagerRequest; import io.github.dsheirer.gui.playlist.ViewPlaylistRequest; +import io.github.dsheirer.gui.preference.CalibrateRequest; +import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; +import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.log.ApplicationLog; import io.github.dsheirer.map.MapService; @@ -55,6 +58,9 @@ import io.github.dsheirer.spectrum.ShowTunerMenuItem; import io.github.dsheirer.spectrum.SpectralDisplayPanel; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import javafx.application.Platform; +import javafx.scene.control.ButtonType; import jiconfont.icons.font_awesome.FontAwesome; import jiconfont.swing.IconFontSwing; import net.miginfocom.swing.MigLayout; @@ -62,24 +68,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JSeparator; -import javax.swing.KeyStroke; -import javax.swing.UIManager; +import javax.swing.*; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.plaf.metal.MetalLookAndFeel; -import java.awt.AWTException; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.Point; -import java.awt.Robot; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -92,6 +85,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Locale; +import java.util.Optional; public class SDRTrunk implements Listener { @@ -170,6 +164,11 @@ public class SDRTrunk implements Listener EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences); mPlaylistManager = new PlaylistManager(mUserPreferences, mSourceManager, aliasModel, eventLogManager, mIconModel); mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mPlaylistManager); + + CalibrationManager calibrationManager = CalibrationManager.getInstance(mUserPreferences); + final boolean calibrating = !calibrationManager.isCalibrated() && + !mUserPreferences.getVectorCalibrationPreference().isHideCalibrationDialog(); + new ChannelSelectionManager(mPlaylistManager.getChannelModel()); AudioPlaybackManager audioPlaybackManager = new AudioPlaybackManager(mUserPreferences); @@ -215,7 +214,29 @@ public class SDRTrunk implements Listener try { mMainGui.setVisible(true); - autoStartChannels(); + + if(calibrating) + { + Platform.runLater(() -> + { + CalibrationDialog calibrationDialog = mJavaFxWindowManager.getCalibrationDialog(mUserPreferences); + Optional calibrate = calibrationDialog.showAndWait(); + if(calibrate.isPresent() && calibrate.get().getText().equals("Calibrate")) + { + //Request focus and execute calibration + MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.VECTOR_CALIBRATION)); + MyEventBus.getGlobalEventBus().post(new CalibrateRequest()); + } + else + { + autoStartChannels(); + } + }); + } + else + { + autoStartChannels(); + } } catch(Exception e) { diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java index db8754ce..b70e8f8e 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer.java @@ -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,14 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.channelizer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -32,7 +32,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; import io.github.dsheirer.source.tuner.test.TestTuner; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import io.github.dsheirer.spectrum.DFTSize; import io.github.dsheirer.spectrum.SpectrumPanel; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; @@ -40,16 +40,10 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JToggleButton; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.Dimension; -import java.awt.EventQueue; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; @@ -85,8 +79,6 @@ public class ChannelizerViewer extends JFrame public ChannelizerViewer(int channelsPerRow) { mTestTuner = new TestTuner(new UserPreferences()); - -// mChannelCount = (int)(mTestTuner.getTunerController().getUsableBandwidth() / CHANNEL_BANDWIDTH); mChannelCount = (int)(mTestTuner.getTunerController().getBandwidth() / CHANNEL_BANDWIDTH); mChannelsPerRow = channelsPerRow; @@ -310,9 +302,9 @@ public class ChannelizerViewer extends JFrame return tunerChannels; } - public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; @@ -323,20 +315,19 @@ public class ChannelizerViewer extends JFrame mSpectrumPanel.setSampleSize(28); add(mSpectrumPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } @Override @@ -346,10 +337,10 @@ public class ChannelizerViewer extends JFrame } } - public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor { private TunerChannelSource mSource; - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private JToggleButton mLoggingButton; @@ -362,8 +353,7 @@ public class ChannelizerViewer extends JFrame mSpectrumPanel.setSampleSize(32); add(mSpectrumPanel, "span"); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); @@ -371,21 +361,7 @@ public class ChannelizerViewer extends JFrame if(mSource != null) { - mSource.setListener(new Listener() - { - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - if(mLoggingEnabled) - { - mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); - } - - complexBuffer.incrementUserCount(); - mDFTProcessor.receive(complexBuffer); - complexBuffer.decrementUserCount(); - } - }); + mSource.setListener(complexSamples -> mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()))); mSource.start(); } @@ -423,15 +399,13 @@ public class ChannelizerViewer extends JFrame public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { - reusableComplexBuffer.incrementUserCount(); - mDFTProcessor.receive(reusableComplexBuffer); - reusableComplexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); } @Override @@ -441,12 +415,12 @@ public class ChannelizerViewer extends JFrame } } - public class DiscreteChannelPanel extends JPanel implements Listener, ISourceEventProcessor + public class DiscreteChannelPanel extends JPanel implements Listener, ISourceEventProcessor { private final Logger mLog = LoggerFactory.getLogger(DiscreteChannelPanel.class); private TunerChannelSource mSource; - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private JToggleButton mLoggingButton; @@ -472,25 +446,22 @@ public class ChannelizerViewer extends JFrame }); // add(mLoggingButton); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(25000.0)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); if(mSource != null) { - mSource.setListener(new Listener() + mSource.setListener(new Listener() { @Override - public void receive(ReusableComplexBuffer complexBuffer) + public void receive(ComplexSamples complexSamples) { if(mLoggingEnabled) { - mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); + mLog.debug("Samples:" + Arrays.toString(complexSamples.toInterleaved().samples())); } - complexBuffer.incrementUserCount(); - mDFTProcessor.receive(complexBuffer); - complexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); } }); @@ -509,15 +480,13 @@ public class ChannelizerViewer extends JFrame public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { - reusableComplexBuffer.incrementUserCount(); - mDFTProcessor.receive(reusableComplexBuffer); - reusableComplexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); } @Override @@ -579,12 +548,11 @@ public class ChannelizerViewer extends JFrame for(TunerChannelSource tunerChannelSource : sources) { - tunerChannelSource.setListener(new Listener() + tunerChannelSource.setListener(new Listener() { @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples complexSamples) { - reusableComplexBuffer.decrementUserCount(); } }); diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java index 30b907f0..cde0495c 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/ChannelizerViewer2.java @@ -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,14 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.channelizer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -33,7 +33,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; import io.github.dsheirer.source.tuner.test.TestTuner; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import io.github.dsheirer.spectrum.DFTSize; import io.github.dsheirer.spectrum.SpectrumPanel; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; @@ -41,22 +41,13 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JToggleButton; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.Dimension; -import java.awt.EventQueue; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class ChannelizerViewer2 extends JFrame @@ -325,9 +316,9 @@ public class ChannelizerViewer2 extends JFrame return tunerChannels; } - public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; @@ -338,20 +329,19 @@ public class ChannelizerViewer2 extends JFrame mSpectrumPanel.setSampleSize(16); add(mSpectrumPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } @Override @@ -361,10 +351,10 @@ public class ChannelizerViewer2 extends JFrame } } - public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor { private TunerChannelSource mSource; - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private JToggleButton mLoggingButton; @@ -377,8 +367,7 @@ public class ChannelizerViewer2 extends JFrame mSpectrumPanel.setSampleSize(16); add(mSpectrumPanel, "span"); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate)); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); mComplexDecibelConverter.addListener(mSpectrumPanel); TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); @@ -387,20 +376,9 @@ public class ChannelizerViewer2 extends JFrame if(mSource != null) { mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate()); - mSource.setListener(new Listener() + mSource.setListener((Listener) complexSamples -> { - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - if(mLoggingEnabled) - { - mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples())); - } - - complexBuffer.incrementUserCount(); - mDFTProcessor.receive(complexBuffer); - complexBuffer.decrementUserCount(); - } + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); }); mSource.start(); @@ -423,14 +401,7 @@ public class ChannelizerViewer2 extends JFrame } mLoggingButton = new JToggleButton("Logging"); - mLoggingButton.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - mLoggingEnabled = mLoggingButton.isSelected(); - } - }); + mLoggingButton.addActionListener(e -> mLoggingEnabled = mLoggingButton.isSelected()); } public TunerChannelSource getSource() @@ -440,15 +411,13 @@ public class ChannelizerViewer2 extends JFrame public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - reusableComplexBuffer.incrementUserCount(); - mDFTProcessor.receive(reusableComplexBuffer); - reusableComplexBuffer.decrementUserCount(); + mComplexDftProcessor.receive(nativeBuffer); } @Override diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java new file mode 100644 index 00000000..99dbd6e2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/channelizer/HeterodyneChannelizerViewer.java @@ -0,0 +1,566 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.gui.channelizer; + +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.settings.SettingsManager; +import io.github.dsheirer.source.ISourceEventProcessor; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.SourceException; +import io.github.dsheirer.source.tuner.Tuner; +import io.github.dsheirer.source.tuner.channel.ChannelSpecification; +import io.github.dsheirer.source.tuner.channel.TunerChannel; +import io.github.dsheirer.source.tuner.channel.TunerChannelSource; +import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; +import io.github.dsheirer.source.tuner.test.TestTuner; +import io.github.dsheirer.spectrum.ComplexDftProcessor; +import io.github.dsheirer.spectrum.DFTSize; +import io.github.dsheirer.spectrum.SpectrumPanel; +import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class HeterodyneChannelizerViewer extends JFrame +{ + private final static Logger mLog = LoggerFactory.getLogger(HeterodyneChannelizerViewer.class); + + private static final int CHANNEL_BANDWIDTH = 12500; + private static final int CHANNEL_FFT_FRAME_RATE = 20; + + private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel()); + private JPanel mPrimaryPanel; + private JPanel mControlPanel; + private JLabel mToneFrequencyLabel; + private PrimarySpectrumPanel mPrimarySpectrumPanel; + private ChannelArrayPanel mChannelPanel; + private DiscreteIndexChannelArrayPanel mDiscreteIndexChannelPanel; + private int mChannelCount; + private int mChannelsPerRow; + private long mBaseFrequency = 100000000; //100 MHz + private DFTSize mMainPanelDFTSize = DFTSize.FFT04096; + private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096; + private TestTuner mTestTuner; + + /** + * GUI Test utility for researching channelizers. + */ + public HeterodyneChannelizerViewer() + { + mTestTuner = new TestTuner(new UserPreferences()); + mChannelCount = 5; + mChannelsPerRow = 5; + init(); + } + + private void init() + { + setTitle("Heterodyne Channelizer Viewer"); + setSize(1200, 800); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + setLocationRelativeTo(null); + add(getPrimaryPanel()); + } + + private JPanel getPrimaryPanel() + { + if(mPrimaryPanel == null) + { + mPrimaryPanel = new JPanel(); + mPrimaryPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[][][grow,fill][grow,fill]")); + mPrimaryPanel.add(getSpectrumPanel(), "wrap"); + mPrimaryPanel.add(getControlPanel(), "wrap"); + mPrimaryPanel.add(getChannelArrayPanel(), "wrap"); +// mPrimaryPanel.add(getDiscreteIndexChannelPanel()); + } + + return mPrimaryPanel; + } + + private PrimarySpectrumPanel getSpectrumPanel() + { + if(mPrimarySpectrumPanel == null) + { + mPrimarySpectrumPanel = new PrimarySpectrumPanel(mSettingsManager, + mTestTuner.getTunerController().getSampleRate()); + mPrimarySpectrumPanel.setPreferredSize(new Dimension(1200, 200)); + mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize); + mTestTuner.getTunerController().addBufferListener(mPrimarySpectrumPanel); + } + + return mPrimarySpectrumPanel; + } + + private JPanel getControlPanel() + { + if(mControlPanel == null) + { + mControlPanel = new JPanel(); + mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", "")); + + mControlPanel.add(new JLabel("Tone:"), "align left"); + long minimumFrequency = -(long)mTestTuner.getTunerController().getSampleRate() / 2 + 1; + long maximumFrequency = (long)mTestTuner.getTunerController().getSampleRate() / 2 - 1; + long toneFrequency = 0; + + SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency, + 100); + + model.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent e) + { + long toneFrequency = model.getNumber().longValue(); + mTestTuner.getTunerController().setToneFrequency(toneFrequency); + mToneFrequencyLabel.setText(String.valueOf(getToneFrequency())); + } + }); + + JSpinner spinner = new JSpinner(model); + + mControlPanel.add(spinner); + mControlPanel.add(new JLabel("Hz")); + + mControlPanel.add(new JLabel("Frequency:"), "push,align right"); + mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency())); + mControlPanel.add(mToneFrequencyLabel, "push,align left"); + + mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right"); + } + + return mControlPanel; + } + + private long getToneFrequency() + { + return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency(); + } + + private ChannelArrayPanel getChannelArrayPanel() + { + if(mChannelPanel == null) + { + mChannelPanel = new ChannelArrayPanel(); + } + + return mChannelPanel; + } + + private DiscreteIndexChannelArrayPanel getDiscreteIndexChannelPanel() + { + if(mDiscreteIndexChannelPanel == null) + { + mDiscreteIndexChannelPanel = new DiscreteIndexChannelArrayPanel(); + } + + return mDiscreteIndexChannelPanel; + } + + public class ChannelArrayPanel extends JPanel + { + private final Logger mLog = LoggerFactory.getLogger(ChannelArrayPanel.class); + + public ChannelArrayPanel() + { + int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE; + if(bufferSize % 2 == 1) + { + bufferSize++; + } + + init(); + } + + private void init() + { + setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + + double spectralBandwidth = mTestTuner.getTunerController().getSampleRate(); + double halfSpectralBandwidth = spectralBandwidth / 2.0; + + int channelToLog = -1; + + long baseFrequency = mBaseFrequency + (CHANNEL_BANDWIDTH / 2); + + for(int x = 0; x < mChannelCount; x++) + { + long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH); + + mLog.debug("Channel " + x + "/" + mChannelCount + " Frequency: " + frequency); + + ChannelPanel channelPanel = new ChannelPanel(mSettingsManager, CHANNEL_BANDWIDTH * 2, frequency, CHANNEL_BANDWIDTH, (x == channelToLog)); + channelPanel.setDFTSize(mChannelPanelDFTSize); + + if(x % mChannelsPerRow == mChannelsPerRow - 1) + { + add(channelPanel, "grow,push,wrap 2px"); + } + else + { + add(channelPanel, "grow,push"); + } + } + } + } + + public class DiscreteIndexChannelArrayPanel extends JPanel + { + public DiscreteIndexChannelArrayPanel() + { + int bufferSize = CHANNEL_BANDWIDTH / CHANNEL_FFT_FRAME_RATE; + if(bufferSize % 2 == 1) + { + bufferSize++; + } + + init(); + } + + private void init() + { + setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); + + ChannelSpecification channelSpecification = new ChannelSpecification(25000.0, 12500, 6000.0, 6250.0); + for(int x = 0; x < mChannelCount; x++) + { + TunerChannel tunerChannel = new TunerChannel(100000000, 12500); + TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); + DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x); + channelPanel.setDFTSize(mChannelPanelDFTSize); + + mLog.debug("Testing Channel [" + x + "] is set to [" + source.getTunerChannel().getFrequency() + "]"); + + if(x % mChannelsPerRow == mChannelsPerRow - 1) + { + add(channelPanel, "grow,push,wrap 2px"); + } + else + { + add(channelPanel, "grow,push"); + } + } + } + } + + /** + * Returns a list of tuner channels that will fit within the tuner's bandwidth, minus a half channel each at the + * lower and upper ends of the spectrum. + * + * @param tuner to create channels for + * @return list of contiguous channels filling the tuner bandwidth + */ + public static List getTunerChannels(Tuner tuner) + { + List tunerChannels = new ArrayList<>(); + + long baseFrequency = tuner.getTunerController().getFrequency(); + baseFrequency -= tuner.getTunerController().getSampleRate() / 2; + baseFrequency += (CHANNEL_BANDWIDTH / 2); + + int channelCount = (int)(tuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH) - 1; + + for(int x = 0; x < channelCount; x++) + { + long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH); + TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH); + tunerChannels.add(tunerChannel); + } + + return tunerChannels; + } + + public class PrimarySpectrumPanel extends JPanel implements Listener, ISourceEventProcessor + { + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + + public PrimarySpectrumPanel(SettingsManager settingsManager, double sampleRate) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(28); + add(mSpectrumPanel); + + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDecibelConverter.addListener(mSpectrumPanel); + } + + public void setDFTSize(DFTSize dftSize) + { + mComplexDftProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(INativeBuffer nativeBuffer) + { + mComplexDftProcessor.receive(nativeBuffer); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + public class ChannelPanel extends JPanel implements Listener, ISourceEventProcessor + { + private TunerChannelSource mSource; + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + private JToggleButton mLoggingButton; + private boolean mLoggingEnabled; + + public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth, boolean enableLogging) + { + setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill][]", "[grow,fill][]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(32); + add(mSpectrumPanel, "span"); + + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDecibelConverter.addListener(mSpectrumPanel); + + TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth); + ChannelSpecification channelSpecification = new ChannelSpecification(25000.0, 12500, 6000.0, 6250.0); + mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification); + + if(mSource != null) + { + mSource.setListener(complexSamples -> mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()))); + + mSource.start(); + } + else + { + mLog.error("Couldn't get a source from the tuner for frequency: " + frequency); + } + + if(mSource != null) + { + add(new JLabel("Center:" + frequency)); + } + else + { + add(new JLabel("NO SRC:" + frequency)); + } + + mLoggingButton = new JToggleButton("Logging"); + mLoggingButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mLoggingEnabled = mLoggingButton.isSelected(); + } + }); +// add(mLoggingButton); + + } + + public TunerChannelSource getSource() + { + return mSource; + } + + public void setDFTSize(DFTSize dftSize) + { + mComplexDftProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ComplexSamples complexSamples) + { + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + public class DiscreteChannelPanel extends JPanel implements Listener, ISourceEventProcessor + { + private final Logger mLog = LoggerFactory.getLogger(DiscreteChannelPanel.class); + + private TunerChannelSource mSource; + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); + private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); + private SpectrumPanel mSpectrumPanel; + private JToggleButton mLoggingButton; + private boolean mLoggingEnabled; + + public DiscreteChannelPanel(SettingsManager settingsManager, TunerChannelSource source, int index) + { + mSource = source; + setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill][]", "[grow,fill][]")); + mSpectrumPanel = new SpectrumPanel(settingsManager); + mSpectrumPanel.setSampleSize(32); + add(mSpectrumPanel, "span"); + add(new JLabel("Index:" + index)); + + mLoggingButton = new JToggleButton("Logging"); + mLoggingButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mLoggingEnabled = mLoggingButton.isSelected(); + } + }); +// add(mLoggingButton); + + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDecibelConverter.addListener(mSpectrumPanel); + + if(mSource != null) + { + mSource.setListener(new Listener() + { + @Override + public void receive(ComplexSamples complexSamples) + { + if(mLoggingEnabled) + { + mLog.debug("Samples:" + Arrays.toString(complexSamples.toInterleaved().samples())); + } + + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); + } + }); + + mSource.start(); + } + else + { + mLog.error("Couldn't get a source from the tuner for index: " + index); + } + } + + public TunerChannelSource getSource() + { + return mSource; + } + + public void setDFTSize(DFTSize dftSize) + { + mComplexDftProcessor.setDFTSize(dftSize); + } + + @Override + public void receive(ComplexSamples complexSamples) + { + mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved())); + } + + @Override + public void process(SourceEvent event) throws SourceException + { + mLog.debug("Source Event! Add handler support for this to channelizer viewer"); + } + } + + + public static void main(String[] args) + { + boolean useGUI = true; + + if(useGUI) + { + final HeterodyneChannelizerViewer frame = new HeterodyneChannelizerViewer(); + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + frame.setVisible(true); + } + }); + } + else + { + TestTuner tuner = new TestTuner(new UserPreferences()); + + List tunerChannels = getTunerChannels(tuner); + + List sources = new ArrayList<>(); + + int maxSourceCount = 30; + int sourceCount = 0; + for(TunerChannel tunerChannel : tunerChannels) + { + if(sourceCount < maxSourceCount) + { + TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel, null); + + if(source != null) + { + sources.add(source); + sourceCount++; + } + else + { + mLog.debug("Couldn't get source for: " + tunerChannel); + } + } + } + + mLog.debug("Starting [" + sources.size() + "] tuner channel sources"); + + for(TunerChannelSource tunerChannelSource : sources) + { + tunerChannelSource.setListener(new Listener() + { + @Override + public void receive(ComplexSamples complexSamples) + { + } + }); + + tunerChannelSource.start(); + } + + while(true) + { + ; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java index 39b3b3c4..4518134e 100644 --- a/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java +++ b/src/main/java/io/github/dsheirer/gui/channelizer/SynthesizerViewer.java @@ -1,35 +1,36 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.gui.channelizer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2; import io.github.dsheirer.dsp.filter.design.FilterDesignException; -import io.github.dsheirer.dsp.mixer.FS4DownConverter; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; -import io.github.dsheirer.sample.IOverflowListener; +import io.github.dsheirer.dsp.oscillator.FS4DownConverter; +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.settings.SettingsManager; -import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import io.github.dsheirer.spectrum.DFTSize; import io.github.dsheirer.spectrum.SpectrumPanel; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; @@ -38,15 +39,10 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.Dimension; -import java.awt.EventQueue; +import java.awt.*; import java.util.concurrent.TimeUnit; public class SynthesizerViewer extends JFrame @@ -141,9 +137,9 @@ public class SynthesizerViewer extends JFrame return mChannel2Panel; } - public class PrimarySpectrumPanel extends JPanel implements Listener + public class PrimarySpectrumPanel extends JPanel implements Listener { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; @@ -154,36 +150,26 @@ public class SynthesizerViewer extends JFrame mSpectrumPanel.setSampleSize(16); add(mSpectrumPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE)); - mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDftProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); mComplexDecibelConverter.addListener(mSpectrumPanel); - - mDFTProcessor.setOverflowListener(new IOverflowListener() - { - @Override - public void sourceOverflow(boolean overflow) - { - mLog.debug("Buffer " + (overflow ? "overflow" : "reset")); - } - }); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } } - public class ChannelPanel extends JPanel implements Listener + public class ChannelPanel extends JPanel implements Listener { - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX); + private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor(); private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter(); private SpectrumPanel mSpectrumPanel; private boolean mLoggingEnabled = false; @@ -196,22 +182,21 @@ public class SynthesizerViewer extends JFrame add(mSpectrumPanel, "wrap"); add(channelControlPanel); - mDFTProcessor.addConverter(mComplexDecibelConverter); - mDFTProcessor.process(SourceEvent.sampleRateChange(CHANNEL_SAMPLE_RATE)); - mDFTProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); + mComplexDftProcessor.addConverter(mComplexDecibelConverter); + mComplexDftProcessor.setFrameRate(CHANNEL_FFT_FRAME_RATE); mComplexDecibelConverter.addListener(mSpectrumPanel); } public void setDFTSize(DFTSize dftSize) { - mDFTProcessor.setDFTSize(dftSize); + mComplexDftProcessor.setDFTSize(dftSize); } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer nativeBuffer) { - mDFTProcessor.receive(reusableComplexBuffer); + mComplexDftProcessor.receive(nativeBuffer); } } @@ -241,7 +226,7 @@ public class SynthesizerViewer extends JFrame private static final int MAX_FREQUENCY = 6250; private static final int DEFAULT_FREQUENCY = 50; - private IOscillator mOscillator = new LowPhaseNoiseOscillator(DEFAULT_FREQUENCY, CHANNEL_SAMPLE_RATE); + private IComplexOscillator mOscillator = OscillatorFactory.getComplexOscillator(DEFAULT_FREQUENCY, CHANNEL_SAMPLE_RATE); public ChannelControlPanel() { @@ -264,7 +249,7 @@ public class SynthesizerViewer extends JFrame add(new JLabel("Hz")); } - public IOscillator getOscillator() + public IComplexOscillator getOscillator() { return mOscillator; } @@ -275,7 +260,6 @@ public class SynthesizerViewer extends JFrame private TwoChannelSynthesizerM2 mSynthesizer; private FS4DownConverter mFS4DownConverter = new FS4DownConverter(); private int mSamplesPerCycle = CHANNEL_SAMPLE_RATE / DATA_GENERATOR_FRAME_RATE; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("SynthesizerViewer"); public DataGenerationManager() { @@ -293,22 +277,15 @@ public class SynthesizerViewer extends JFrame @Override public void run() { - ReusableComplexBuffer channel1Buffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerCycle); - getChannel1ControlPanel().getOscillator().generateComplex(channel1Buffer); + ComplexSamples channel1Buffer = getChannel1ControlPanel().getOscillator().generateComplexSamples(mSamplesPerCycle); + ComplexSamples channel2Buffer = getChannel2ControlPanel().getOscillator().generateComplexSamples(mSamplesPerCycle); - ReusableComplexBuffer channel2Buffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerCycle); - getChannel2ControlPanel().getOscillator().generateComplex(channel2Buffer); + ComplexSamples synthesizedBuffer = mSynthesizer.process(channel1Buffer, channel2Buffer); + getChannel1Panel().receive(new FloatNativeBuffer(channel1Buffer, System.currentTimeMillis())); - channel1Buffer.incrementUserCount(); - channel2Buffer.incrementUserCount(); + getChannel2Panel().receive(new FloatNativeBuffer(channel2Buffer, System.currentTimeMillis())); - ReusableComplexBuffer synthesizedBuffer = mSynthesizer.process(channel1Buffer, channel2Buffer); - - getChannel1Panel().receive(channel1Buffer); - - getChannel2Panel().receive(channel2Buffer); - - getSpectrumPanel().receive(synthesizedBuffer); + getSpectrumPanel().receive(new FloatNativeBuffer(synthesizedBuffer, System.currentTimeMillis())); } } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java b/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java index 41019694..2987b195 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/PlaybackController.java @@ -1,30 +1,27 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; import io.github.dsheirer.source.wave.ComplexWaveSource; @@ -65,7 +62,8 @@ public class PlaybackController extends HBox implements IFrameLocationListener private AbstractDecoderPane mSampleRateListener; private IControllableFileSource mControllableFileSource; - private ReusableBufferBroadcaster mReusableBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mRealBufferBroadcaster = new Broadcaster<>(); + private Broadcaster mNativeBufferBroadcaster = new Broadcaster<>(); public PlaybackController() { @@ -96,7 +94,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener { mControllableFileSource = new ComplexWaveSource(file); mControllableFileSource.setListener(this); - ((ComplexWaveSource)mControllableFileSource).setListener(mReusableBufferBroadcaster); + ((ComplexWaveSource)mControllableFileSource).setListener(mNativeBufferBroadcaster); mControllableFileSource.open(); if(mSampleRateListener != null) @@ -130,7 +128,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener { mControllableFileSource = new RealWaveSource(file); mControllableFileSource.setListener(this); - ((RealWaveSource)mControllableFileSource).setListener(mReusableBufferBroadcaster); + ((RealWaveSource)mControllableFileSource).setListener(mRealBufferBroadcaster); mControllableFileSource.open(); if(mSampleRateListener != null) @@ -181,19 +179,35 @@ public class PlaybackController extends HBox implements IFrameLocationListener } /** - * Adds listener to receive complex buffers from this playback + * Adds listener to receive real buffers from this playback */ - public void addListener(Listener listener) + public void addRealListener(Listener listener) { - mReusableBufferBroadcaster.addListener(listener); + mRealBufferBroadcaster.addListener(listener); } /** - * Removes the listener from receiving complex buffers from this playback + * Removes the listener from receiving real buffers from this playback */ - public void removeListener(Listener listener) + public void removeRealListener(Listener listener) { - mReusableBufferBroadcaster.removeListener(listener); + mRealBufferBroadcaster.removeListener(listener); + } + + /** + * Adds listener to receive complex buffers from this playback + */ + public void addComplexListener(Listener listener) + { + mNativeBufferBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving compex buffers from this playback + */ + public void removeComplexListener(Listener listener) + { + mNativeBufferBroadcaster.removeListener(listener); } /** diff --git a/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java b/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java index f5e5034b..d94b2183 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/ViewerDesktop.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,12 +14,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.instrument; import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane; import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory; +import io.github.dsheirer.gui.instrument.decoder.RealDecoderPane; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder; import javafx.scene.layout.BorderPane; @@ -56,11 +56,26 @@ public class ViewerDesktop extends BorderPane private void setDecoderPane(AbstractDecoderPane decoderPane) { - getPlaybackController().removeListener(getDecoderPane()); + if(getDecoderPane() instanceof RealDecoderPane) + { + getPlaybackController().removeRealListener(getDecoderPane()); + } + else + { + getPlaybackController().removeComplexListener(getDecoderPane()); + } getChildren().remove(getDecoderPane()); mDecoderPane = decoderPane; setCenter(getDecoderPane()); - getPlaybackController().addListener(getDecoderPane()); + + if(getDecoderPane() instanceof RealDecoderPane) + { + getPlaybackController().addRealListener(getDecoderPane()); + } + else + { + getPlaybackController().addComplexListener(getDecoderPane()); + } getPlaybackController().setSampleRateListener(getDecoderPane()); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java deleted file mode 100644 index 0d6a15c6..00000000 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexPhaseLineChart.java +++ /dev/null @@ -1,124 +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 - * - ******************************************************************************/ -package io.github.dsheirer.gui.instrument.chart; - -import io.github.dsheirer.buffer.ComplexCircularBuffer; -import io.github.dsheirer.dsp.gain.ComplexGain; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.complex.Complex; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.chart.LineChart; -import javafx.scene.chart.NumberAxis; -import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ComplexPhaseLineChart extends LineChart implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(ComplexPhaseLineChart.class); - - private static final Complex ANGLE_OFFSET_45_DEGREES = Complex.fromAngle(FastMath.PI / 4.0); - private ComplexCircularBuffer mComplexCircularBuffer; - private ComplexGain mComplexGain = new ComplexGain(600.0f); - private ObservableList> mPhaseValues = FXCollections.observableArrayList(); - private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40); - - public ComplexPhaseLineChart(int length) - { - super(new NumberAxis("Time", 1, length - 10, 2), - new NumberAxis("Phase", -FastMath.PI, FastMath.PI, FastMath.PI / 2.0)); - - Series phaseSeries = new Series<>("Phase", mPhaseValues); - ObservableList> observableList = FXCollections.observableArrayList(phaseSeries); - - setData(observableList); - init(length); - } - - private void init(int length) - { - mLengthProperty.addListener(new ChangeListener() - { - @Override - public void changed(ObservableValue observable, Number oldValue, Number newValue) - { - mComplexCircularBuffer = new ComplexCircularBuffer(newValue.intValue()); - } - }); - mComplexCircularBuffer = new ComplexCircularBuffer(length); - length().setValue(length); - - for(int x = 10; x <= length; x++) - { - mPhaseValues.add(new Data(x - 10, 0.0f)); - } - } - - /** - * Length of the x-axis for displaying samples - */ - public IntegerProperty length() - { - return mLengthProperty; - } - - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - float[] samples = complexBuffer.getSamples(); - - Complex sample; - - for(int x = 0; x < samples.length; x += 2) - { - sample = new Complex(samples[x], samples[x + 1]); - Complex copy = sample.copy(); - mComplexGain.apply(copy); - mComplexCircularBuffer.put(copy); - } - - complexBuffer.decrementUserCount(); - - updateChart(); - } - - private void updateChart() - { - Complex[] samples = mComplexCircularBuffer.getAll(); - - for(int x = 10; x < samples.length; x++) - { - Complex previous = samples[x - 10].copy(); - Complex sample = samples[x].copy(); - - if(previous != null && sample != null) - { -// previous.normalize(); -// sample.normalize(); - previous.add(ANGLE_OFFSET_45_DEGREES); - sample.multiply(previous.conjugate()); -// sample.normalize(); - mPhaseValues.get(x - 9).setYValue(sample.angle()); - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java index 55d19566..ffe65c67 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/ComplexSampleLineChart.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument.chart; import io.github.dsheirer.buffer.ComplexCircularBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; import io.github.dsheirer.sample.complex.ComplexSampleListener; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; @@ -38,7 +35,7 @@ import javafx.scene.chart.XYChart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ComplexSampleLineChart extends LineChart implements Listener, ComplexSampleListener +public class ComplexSampleLineChart extends LineChart implements Listener, ComplexSampleListener { private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class); @@ -97,16 +94,12 @@ public class ComplexSampleLineChart extends LineChart implements Listener - * - ******************************************************************************/ -package io.github.dsheirer.gui.instrument.chart; - -import io.github.dsheirer.buffer.DoubleCircularBuffer; -import io.github.dsheirer.dsp.fm.FMDemodulator; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.complex.Complex; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.chart.LineChart; -import javafx.scene.chart.NumberAxis; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FMDemodulatedLineChart extends LineChart implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatedLineChart.class); - - private DoubleCircularBuffer mDoubleCircularBuffer; - private int mSamplesPerSymbol; - private int mTwiceSamplesPerSymbol; - private int mSeriesCount; - private ObservableList> mData = FXCollections.observableArrayList(); - private Complex mPreviousSample = new Complex(0,0); - private int mSeriesPointer; - private int mSamplePointer; - - public FMDemodulatedLineChart(int samplesPerSymbol, int seriesCount) - { - super(new NumberAxis("Time", 1, samplesPerSymbol * 2, 2), - new NumberAxis("Demodulated", -1.0, 1.0, 0.25)); - - mSamplesPerSymbol = samplesPerSymbol; - mTwiceSamplesPerSymbol = mSamplesPerSymbol * 2; - mSeriesCount = seriesCount; - - init(); - } - - private void init() - { - int seriesLength = 2 * mSamplesPerSymbol; - mDoubleCircularBuffer = new DoubleCircularBuffer(seriesLength * mSeriesCount); - - for(int x = 0; x < mSeriesCount; x++) - { - ObservableList> series = FXCollections.observableArrayList(); - - for(int y = 0; y < seriesLength; y++) - { - series.add(new Data(y + 1, 0.0)); - } - - mData.add(new Series<>(series)); - } - - setData(mData); - } - - @Override - public void receive(ReusableComplexBuffer complexBuffer) - { - float[] samples = complexBuffer.getSamples(); - - Complex sample; - - for(int x = 0; x < samples.length; x += 2) - { - sample = new Complex(samples[x], samples[x + 1]); - double demodulated = FMDemodulator.demodulate(mPreviousSample, sample); - demodulated *= 2.0; - - if(demodulated > 1.0) - { - demodulated = 1.0; - } - else if(demodulated < -1.0) - { - demodulated = -1.0; - } - mDoubleCircularBuffer.put(demodulated); - mPreviousSample = sample; - updateChart(demodulated); - } - - complexBuffer.decrementUserCount(); - } - - private void updateChart(double value) - { - Series series = mData.get(mSeriesPointer); - Data data = series.getData().get(mSamplePointer++); - data.setYValue(value); - - if(mSamplePointer >= mTwiceSamplesPerSymbol) - { - mSamplePointer = 0; - mSeriesPointer++; - - if(mSeriesPointer >= mSeriesCount) - { - mSeriesPointer = 0; - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java index 708518f3..41ea1522 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/RealSampleLineChart.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.gui.instrument.chart; import io.github.dsheirer.buffer.RealCircularBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.chart.LineChart; @@ -27,7 +29,7 @@ import javafx.scene.layout.StackPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RealSampleLineChart extends LineChart implements Listener +public class RealSampleLineChart extends LineChart implements Listener { private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class); private ObservableList> mSamples = FXCollections.observableArrayList(); @@ -65,9 +67,9 @@ public class RealSampleLineChart extends LineChart implements Listener sample = mSamples.get(x); sample.setYValue(bufferSamples[x]); } - - buffer.decrementUserCount(); } } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java index 6dc963c8..83991a71 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/SampleXYChart.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument.chart; @@ -28,15 +25,15 @@ import eu.hansolo.fx.charts.data.XYChartItem; import eu.hansolo.fx.charts.series.XYSeries; import io.github.dsheirer.buffer.ComplexCircularBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SampleXYChart extends PolarChart implements Listener +public class SampleXYChart extends PolarChart implements Listener { private static ObservableList sChartItemList = FXCollections.observableArrayList(); @@ -67,17 +64,13 @@ public class SampleXYChart extends PolarChart implements Listener series = getXYPane().getListOfSeries().get(0); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java index 546b807d..beaf818c 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.gui.instrument.decoder; import io.github.dsheirer.gui.instrument.chart.AFSK1200SampleBufferChart; @@ -23,7 +26,6 @@ import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -52,7 +54,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane private void init() { //This is force-cast without type checking .... - addListener((Listener) getDecoder()); + addListener((Listener) getDecoder()); addListener(getSampleLineChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java index 5c324989..f87c8c11 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractDecoderPane.java @@ -1,26 +1,28 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.gui.instrument.decoder; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.scene.layout.VBox; -public abstract class AbstractDecoderPane extends VBox implements Listener +public abstract class AbstractDecoderPane extends VBox implements Listener { abstract SampleType getSampleType(); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java index 0bd39097..acf71e4a 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/ComplexDecoderPane.java @@ -1,30 +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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.gui.instrument.decoder; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import javafx.scene.text.Text; -public class ComplexDecoderPane extends AbstractDecoderPane +public class ComplexDecoderPane extends AbstractDecoderPane { - private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mBufferBroadcaster = new Broadcaster(); private DecoderType mDecoderType; public ComplexDecoderPane(DecoderType decoderType) @@ -47,18 +50,18 @@ public class ComplexDecoderPane extends AbstractDecoderPane listener) + public void addListener(Listener listener) { - mComplexBufferBroadcaster.addListener(listener); + mBufferBroadcaster.addListener(listener); } - public void removeListener(Listener listener) + public void removeListener(Listener listener) { - mComplexBufferBroadcaster.removeListener(listener); + mBufferBroadcaster.removeListener(listener); } } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java index 53338a3a..6e704215 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument.decoder; @@ -26,17 +23,18 @@ import io.github.dsheirer.gui.instrument.chart.DoubleLineChart; import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart; import io.github.dsheirer.gui.instrument.chart.PhaseLineChart; import io.github.dsheirer.gui.instrument.chart.SymbolChart; -import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.dmr.DMRDecoderInstrumented; import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.Broadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class DMRPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(DMRPane.class); @@ -49,7 +47,7 @@ public class DMRPane extends ComplexDecoderPane private PhaseLineChart mPLLPhaseErrorLineChart; private DoubleLineChart mPLLFrequencyLineChart; private DoubleLineChart mSamplesPerSymbolLineChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR()); public DMRPane() @@ -60,7 +58,15 @@ public class DMRPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); getDecoder().setComplexSymbolListener(getSymbolChart()); @@ -74,14 +80,7 @@ public class DMRPane extends ComplexDecoderPane HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS); getChildren().addAll(getSampleChartBox(), getDecoderChartBox()); - mDecoder.setMessageListener(new Listener() - { - @Override - public void receive(IMessage message) - { - mLog.debug(message.toString()); - } - }); + mDecoder.setMessageListener(message -> mLog.debug(message.toString())); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java index 853d7dbd..deb7adfe 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument.decoder; @@ -29,13 +26,16 @@ import io.github.dsheirer.gui.instrument.chart.SymbolChart; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FMInstrumented; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class P25Phase1C4FMPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(P25Phase1C4FMPane.class); @@ -48,7 +48,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane private PhaseLineChart mPLLPhaseErrorLineChart; private DoubleLineChart mPLLFrequencyLineChart; private DoubleLineChart mSamplesPerSymbolLineChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private P25P1DecoderC4FMInstrumented mDecoder = new P25P1DecoderC4FMInstrumented(); public P25Phase1C4FMPane() @@ -59,7 +59,15 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); getDecoder().setComplexSymbolListener(getSymbolChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java index 56c87588..a5d95169 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument.decoder; @@ -30,13 +27,16 @@ import io.github.dsheirer.gui.instrument.chart.SymbolChart; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSMInstrumented; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class P25Phase1LSMPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class); @@ -49,7 +49,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane private PhaseLineChart mPLLPhaseErrorLineChart; private DoubleLineChart mPLLFrequencyLineChart; private SamplesPerSymbolChart mSamplesPerSymbolLineChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private P25P1DecoderLSMInstrumented mDecoder = new P25P1DecoderLSMInstrumented(); public P25Phase1LSMPane() @@ -60,7 +60,15 @@ public class P25Phase1LSMPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); getDecoder().setComplexSymbolListener(getSymbolConstellationChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java index 833fed72..9a017b02 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase2HDQPSKPane.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.instrument.decoder; @@ -31,13 +28,16 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSKInstrumented; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.ComplexSamples; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Iterator; + public class P25Phase2HDQPSKPane extends ComplexDecoderPane { private final static Logger mLog = LoggerFactory.getLogger(P25Phase2HDQPSKPane.class); @@ -56,7 +56,7 @@ public class P25Phase2HDQPSKPane extends ComplexDecoderPane private SampleXYChart mSampleXYChart; - private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); private P25P2DecoderHDQPSKInstrumented mDecoder = new P25P2DecoderHDQPSKInstrumented(new DecodeConfigP25Phase2()); public P25Phase2HDQPSKPane() @@ -67,7 +67,15 @@ public class P25Phase2HDQPSKPane extends ComplexDecoderPane private void init() { - addListener(getDecoder()); + addListener(nativeBuffer -> + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + getDecoder().receive(iterator.next()); + } + }); getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart()); diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java index 1b2f15f1..7bff926c 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/RealDecoderPane.java @@ -1,30 +1,32 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.gui.instrument.decoder; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.scene.text.Text; -public class RealDecoderPane extends AbstractDecoderPane +public class RealDecoderPane extends AbstractDecoderPane { - private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mBufferBroadcaster = new Broadcaster(); private DecoderType mDecoderType; public RealDecoderPane(DecoderType decoderType) @@ -47,17 +49,17 @@ public class RealDecoderPane extends AbstractDecoderPane } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] buffer) { - mBufferBroadcaster.broadcast(reusableFloatBuffer); + mBufferBroadcaster.broadcast(buffer); } - public void addListener(Listener listener) + public void addListener(Listener listener) { mBufferBroadcaster.addListener(listener); } - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mBufferBroadcaster.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java index cd93cd62..b9614cf5 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.playlist; @@ -176,7 +173,7 @@ public class PlaylistEditor extends BorderPane String filename = TimeStamp.getTimeStamp("_") + "_screen_capture.png"; final Path captureFile = mUserPreferences.getDirectoryPreference().getDirectoryScreenCapture().resolve(filename); - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.submit(() -> { try { ImageIO.write(bufferedImage, "png", captureFile.toFile()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java index 07dbafab..def7a7d8 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/BeepEditor.java @@ -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 @@ -137,7 +137,7 @@ public class BeepEditor extends ActionEditor final TestMessage testMessage = new TestMessage(); final Alias testAlias = new Alias("Test Alias"); - ThreadPool.SCHEDULED.submit(() -> { + ThreadPool.CACHED.submit(() -> { try { action.execute(testAlias, testMessage); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java index 0736c032..e88452ce 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ClipEditor.java @@ -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 @@ -200,7 +200,7 @@ public class ClipEditor extends ActionEditor final TestMessage testMessage = new TestMessage(); final Alias testAlias = new Alias("Test Alias"); - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java index 8a5c02e1..d5efc61b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/action/ScriptEditor.java @@ -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 @@ -200,7 +200,7 @@ public class ScriptEditor extends ActionEditor final TestMessage testMessage = new TestMessage(); final Alias testAlias = new Alias("Test Alias"); - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java index e1494896..9ff926c5 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.playlist.channel; @@ -365,7 +362,7 @@ public abstract class ChannelConfigurationEditor extends Editor if(!getItem().processingProperty().get()) { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { mPlaylistManager.getChannelProcessingManager().start(getItem()); @@ -386,7 +383,7 @@ public abstract class ChannelConfigurationEditor extends Editor } else { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { mPlaylistManager.getChannelProcessingManager().stop(getItem()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java index 5222597d..7cba16ab 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyEditor.java @@ -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 @@ -155,7 +155,7 @@ public class AgencyEditor extends VBox mUserPreferences.getRadioReferencePreference().setPreferredAgencyId(agency.getAgencyId(), mLevel); - ThreadPool.SCHEDULED.submit(() -> { + ThreadPool.CACHED.submit(() -> { try { final AgencyInfo agencyInfo = mRadioReference.getService().getAgencyInfo(agency); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java index f11e1cd4..db5f42e3 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/AgencyFrequencyEditor.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.playlist.radioreference; @@ -400,7 +397,7 @@ public class AgencyFrequencyEditor extends GridPane if(subCategory != null) { - ThreadPool.SCHEDULED.submit(() -> { + ThreadPool.CACHED.submit(() -> { try { List frequencies = new ArrayList<>(); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java index 90b76992..cb48e837 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/FrequencyEditor.java @@ -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 @@ -230,7 +230,7 @@ public class FrequencyEditor extends VBox { if(modeId != null) { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { Integer parsed = null; try diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java index 8667fb88..9530a130 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceEditor.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.playlist.radioreference; @@ -44,7 +41,13 @@ import javafx.scene.control.Label; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextField; -import javafx.scene.layout.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.util.StringConverter; import jiconfont.icons.font_awesome.FontAwesome; @@ -119,7 +122,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer accept(credentials)); + ThreadPool.CACHED.execute(() -> accept(credentials)); } } } @@ -134,7 +137,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { List countries = mRadioReference.getService().getCountries(); @@ -565,7 +568,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { final CountryInfo countryInfo = mRadioReference.getService().getCountryInfo(country.getCountryId()); @@ -623,7 +626,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { final StateInfo stateInfo = mRadioReference.getService().getStateInfo(state.getStateId()); @@ -679,7 +682,7 @@ public class RadioReferenceEditor extends BorderPane implements Consumer { + ThreadPool.CACHED.execute(() -> { try { final CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(county.getCountyId()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java index 6217fc72..26523033 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemEditor.java @@ -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 @@ -276,7 +276,7 @@ public class SystemEditor extends VBox mUserPreferences.getRadioReferencePreference().setPreferredSystemId(system.getSystemId(), mLevel); //Retrieve the radio reference data on a separate thread and then load the editors on the FX thread - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { if(mRadioReferenceDecoder == null) diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java index da505e72..102f67c2 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamingEditor.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.gui.playlist.streaming; @@ -234,7 +231,7 @@ public class StreamingEditor extends SplitPane { if(mPlaylistManager.getRadioReference().availableProperty().get()) { - ThreadPool.SCHEDULED.submit(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java b/src/main/java/io/github/dsheirer/gui/preference/CalibrateRequest.java similarity index 67% rename from src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java rename to src/main/java/io/github/dsheirer/gui/preference/CalibrateRequest.java index bd686ddd..7eaafbb5 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/ComplexDecimateX0Filter.java +++ b/src/main/java/io/github/dsheirer/gui/preference/CalibrateRequest.java @@ -17,24 +17,15 @@ * **************************************************************************** */ -package io.github.dsheirer.dsp.filter.decimate; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +package io.github.dsheirer.gui.preference; /** - * No decimation filter. + * View calibration preference and execute calibration request */ -public class ComplexDecimateX0Filter implements IComplexDecimationFilter +public class CalibrateRequest extends ViewUserPreferenceEditorRequest { - @Override - public float[] decimateComplex(float[] samples) + public CalibrateRequest() { - return samples; - } - - @Override - public ReusableComplexBuffer decimate(ReusableComplexBuffer buffer) - { - return buffer; + super(PreferenceEditorType.VECTOR_CALIBRATION); } } diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java index 39da9e07..7baf12bc 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java @@ -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 @@ -19,6 +19,7 @@ package io.github.dsheirer.gui.preference; +import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor; import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor; import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor; import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor; @@ -53,6 +54,8 @@ public class PreferenceEditorFactory return new TunerPreferenceEditor(userPreferences); case TALKGROUP_FORMAT: return new TalkgroupFormatPreferenceEditor(userPreferences); + case VECTOR_CALIBRATION: + return new VectorCalibrationPreferenceEditor(userPreferences); } return null; diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java index de2263fd..de98f775 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java @@ -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 @@ -32,6 +32,7 @@ public enum PreferenceEditorType AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"), SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"), TALKGROUP_FORMAT("Talkgroup & Radio ID"), + VECTOR_CALIBRATION("Vector Calibration"), DEFAULT("Default"); private String mLabel; diff --git a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java index 69e7e3ad..78e12781 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java @@ -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 @@ -173,6 +173,11 @@ public class UserPreferencesEditor extends BorderPane treeRoot.getChildren().add(audioItem); audioItem.setExpanded(true); + TreeItem cpuItem = new TreeItem<>("CPU"); + cpuItem.getChildren().add(new TreeItem(PreferenceEditorType.VECTOR_CALIBRATION)); + treeRoot.getChildren().add(cpuItem); + cpuItem.setExpanded(true); + TreeItem decoderItem = new TreeItem<>("Decoder"); decoderItem.getChildren().add(new TreeItem(PreferenceEditorType.JMBE_LIBRARY)); treeRoot.getChildren().add(decoderItem); diff --git a/src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java b/src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java new file mode 100644 index 00000000..5f9f138d --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/calibration/CalibrationDialog.java @@ -0,0 +1,73 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.gui.preference.calibration; + +import io.github.dsheirer.preference.UserPreferences; +import javafx.geometry.Insets; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +/** + * Dialog to prompt the user to run the calibrations. + */ +public class CalibrationDialog extends Dialog +{ + private UserPreferences mUserPreferences; + + public CalibrationDialog(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + + setTitle("Calibrate CPU"); + setHeaderText("Perform CPU Calibration?"); + + Label instructionsLabel = new Label("The sdrtrunk application can run more efficiently by using Vector or Single " + + "Instruction Multiple Data (SIMD) CPU operations. sdrtrunk must calibrate your CPU in order " + + "to determine the best implementation.\n\n" + + "Calibration takes a few minutes to complete.\n\n" + + "If you click 'Cancel', you will be prompted next time you run sdrtrunk, or you can open the " + + "User Preferences dialog from the View menu and manually start the calibration process."); + instructionsLabel.setPrefWidth(300); + instructionsLabel.setWrapText(true); + + CheckBox doNotShowAgainCheckBox = new CheckBox("Do not show again"); + doNotShowAgainCheckBox.setOnAction(event -> + { + mUserPreferences.getVectorCalibrationPreference().setHideCalibrationDialog(doNotShowAgainCheckBox.isSelected()); + }); + + VBox.setVgrow(instructionsLabel, Priority.ALWAYS); + VBox contentBox = new VBox(); + contentBox.setPadding(new Insets(10)); + contentBox.setSpacing(20); + contentBox.getChildren().addAll(instructionsLabel, doNotShowAgainCheckBox); + + getDialogPane().setContent(contentBox); + + ButtonType calibrateButtonType = new ButtonType("Calibrate", ButtonBar.ButtonData.OK_DONE); + ButtonType cancelButtonType = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(calibrateButtonType, cancelButtonType); + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java new file mode 100644 index 00000000..1c909598 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/calibration/VectorCalibrationPreferenceEditor.java @@ -0,0 +1,347 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.gui.preference.calibration; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.gui.preference.CalibrateRequest; +import io.github.dsheirer.log.TextAreaLogAppender; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; +import io.github.dsheirer.util.ThreadPool; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import javafx.application.Platform; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.controlsfx.control.ToggleSwitch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Preference settings for duplicate call audio handling + */ +public class VectorCalibrationPreferenceEditor extends HBox +{ + private final static Logger mLog = LoggerFactory.getLogger(VectorCalibrationPreferenceEditor.class); + private VectorCalibrationPreference mPreference; + private GridPane mEditorPane; + private ToggleSwitch mHideDialogSwitch; + private ToggleSwitch mVectorEnabled; + private Label mCalibrationsPendingValue; + private Button mResetAllButton; + private Button mCalibrateButton; + private ProgressBar mProgressBar; + private Label mCalibratingLabel; + private TextArea mConsoleTextArea; + private TextAreaLogAppender mTextAreaLogAppender; + + /** + * Constructs an instance + */ + public VectorCalibrationPreferenceEditor(UserPreferences userPreferences) + { + MyEventBus.getGlobalEventBus().register(this); + mPreference = userPreferences.getVectorCalibrationPreference(); + setMaxHeight(Double.MAX_VALUE); + setMaxWidth(Double.MAX_VALUE); + + VBox vbox = new VBox(); + vbox.setMaxHeight(Double.MAX_VALUE); + vbox.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(vbox, Priority.ALWAYS); + + VBox.setVgrow(getEditorPane(), Priority.NEVER); + vbox.getChildren().add(getEditorPane()); + + ScrollPane scrollPane = new ScrollPane(getConsoleTextArea()); + scrollPane.setFitToHeight(true); + scrollPane.setFitToWidth(true); + + VBox.setVgrow(scrollPane, Priority.ALWAYS); + vbox.getChildren().add(scrollPane); + getChildren().add(vbox); + } + + @Subscribe + public void process(CalibrateRequest request) + { + getCalibrateButton().fire(); + } + + private GridPane getEditorPane() + { + if(mEditorPane == null) + { + int row = 0; + mEditorPane = new GridPane(); + mEditorPane.setPadding(new Insets(10)); + mEditorPane.setHgap(10); + mEditorPane.setVgap(10); + + Label titleLabel = new Label("CPU Vector SIMD Preferences"); + GridPane.setConstraints(titleLabel, 0, row++, 2, 1); + mEditorPane.getChildren().add(titleLabel); + + GridPane.setConstraints(getVectorEnabledToggleSwitch(), 0, row); + GridPane.setHalignment(getVectorEnabledToggleSwitch(), HPos.RIGHT); + mEditorPane.getChildren().add(getVectorEnabledToggleSwitch()); + + Label enableSimdLabel = new Label("Enable SIMD Vector Operations"); + GridPane.setConstraints(enableSimdLabel, 1, row++); + mEditorPane.getChildren().add((enableSimdLabel)); + + GridPane.setConstraints(getHideDialogSwitch(), 0, row); + GridPane.setHalignment(getHideDialogSwitch(), HPos.RIGHT); + mEditorPane.getChildren().add(getHideDialogSwitch()); + + Label hideDialogLabel = new Label("Don't Show Calibration Dialog When New Calibrations Are Available"); + GridPane.setConstraints(hideDialogLabel, 1, row++); + mEditorPane.getChildren().add((hideDialogLabel)); + + GridPane.setConstraints(getCalibrationsPendingValue(), 0, row); + mEditorPane.getChildren().add(getCalibrationsPendingValue()); + + Label calibrationsPendingLabel = new Label("Calibrations To Perform"); + GridPane.setHalignment(getCalibrationsPendingValue(), HPos.RIGHT); + GridPane.setConstraints(calibrationsPendingLabel, 1, row++); + mEditorPane.getChildren().add(calibrationsPendingLabel); + updateControls(); + + GridPane.setConstraints(getCalibratingLabel(), 0, row); + mEditorPane.getChildren().add(getCalibratingLabel()); + + GridPane.setConstraints(getProgressBar(), 1, row++); + mEditorPane.getChildren().add(getProgressBar()); + + HBox buttonsBox = new HBox(); + buttonsBox.setSpacing(10); + buttonsBox.getChildren().addAll(getCalibrateButton(), getResetAllButton()); + GridPane.setHalignment(buttonsBox, HPos.CENTER); + GridPane.setConstraints(buttonsBox, 0, row, 2, 1); + mEditorPane.getChildren().add(buttonsBox); + } + + return mEditorPane; + } + + private void enableConsoleLogging() + { + LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Calibration.class); + ((ch.qos.logback.classic.Logger)logger).setLevel(Level.INFO); + ((ch.qos.logback.classic.Logger)logger).addAppender(getTextAreaLogAppender()); + getTextAreaLogAppender().start(); + } + + private void disableConsoleLogging() + { + LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Calibration.class); + ((ch.qos.logback.classic.Logger)logger).detachAppender(getTextAreaLogAppender()); + getTextAreaLogAppender().stop(); + } + + private TextAreaLogAppender getTextAreaLogAppender() + { + if(mTextAreaLogAppender == null) + { + mTextAreaLogAppender = new TextAreaLogAppender(getConsoleTextArea(), "My Console Logger"); + } + + return mTextAreaLogAppender; + } + + private TextArea getConsoleTextArea() + { + if(mConsoleTextArea == null) + { + mConsoleTextArea = new TextArea(); + mConsoleTextArea.setDisable(true); + mConsoleTextArea.setMaxHeight(Double.MAX_VALUE); + mConsoleTextArea.setMaxWidth(Double.MAX_VALUE); + } + + return mConsoleTextArea; + } + + private ProgressBar getProgressBar() + { + if(mProgressBar == null) + { + mProgressBar = new ProgressBar(); + mProgressBar.setVisible(false); + } + + return mProgressBar; + } + + private Button getResetAllButton() + { + if(mResetAllButton == null) + { + mResetAllButton = new Button("Reset All Calibrations"); + mResetAllButton.setTooltip(new Tooltip("Reset all calibrations to an Uncalibrated state")); + mResetAllButton.setOnAction(event -> + { + CalibrationManager.getInstance().reset(); + updateControls(); + }); + } + + return mResetAllButton; + } + + private Button getCalibrateButton() + { + if(mCalibrateButton == null) + { + mCalibrateButton = new Button("Calibrate"); + mCalibrateButton.setTooltip(new Tooltip("Calibrate the items that need calibrated")); + mCalibrateButton.setOnAction(event -> + { + getCalibrateButton().setDisable(true); + getResetAllButton().setDisable(true); + getConsoleTextArea().clear(); + getConsoleTextArea().appendText("Calibrating. Each calibration lasts 20 - 40 seconds."); + enableConsoleLogging(); + getCalibratingLabel().setVisible(true); + getProgressBar().setVisible(true); + getProgressBar().setProgress(0.0); + + ThreadPool.CACHED.submit(() -> + { + try + { + CalibrationManager manager = CalibrationManager.getInstance(); + List calibrations = manager.getUncalibrated(); + int counter = 0; + for(Calibration calibration: calibrations) + { + counter++; + + final String message = "\n\nCalibration [" + counter + "/" + calibrations.size() + "] - " + calibration.getType(); + Platform.runLater(() -> getConsoleTextArea().appendText(message)); + + try + { + calibration.calibrate(); + + final double progress = (double)counter / (double)calibrations.size(); + Platform.runLater(() -> getProgressBar().setProgress(progress)); + } + catch(CalibrationException ce) + { + mLog.error("Calibration error for " + calibration.getType(), ce); + } + } + } + catch(Throwable t) + { + mLog.error("Error while performing calibrations", t); + } + + Platform.runLater(() -> + { + getConsoleTextArea().appendText("\nCalibration Complete!\nNote: restart the application to use these new settings."); + getCalibratingLabel().setVisible(false); + getProgressBar().setVisible(false); + updateControls(); + disableConsoleLogging(); + }); + }); + }); + } + + return mCalibrateButton; + } + + private Label getCalibratingLabel() + { + if(mCalibratingLabel == null) + { + mCalibratingLabel = new Label("Calibrating:"); + mCalibratingLabel.setVisible(false); + } + + return mCalibratingLabel; + } + + private void updateControls() + { + int pending = CalibrationManager.getInstance().getUncalibrated().size(); + int total = CalibrationManager.getInstance().getCalibrationTypes().size(); + getCalibrationsPendingValue().setText(String.valueOf(pending)); + getCalibrateButton().setDisable(pending == 0); + getResetAllButton().setDisable(pending == total); + } + + private Label getCalibrationsPendingValue() + { + if(mCalibrationsPendingValue == null) + { + mCalibrationsPendingValue = new Label(" "); + } + + return mCalibrationsPendingValue; + } + + private ToggleSwitch getVectorEnabledToggleSwitch() + { + if(mVectorEnabled == null) + { + mVectorEnabled = new ToggleSwitch(); + mVectorEnabled.setTooltip(new Tooltip("Allow sdrtrunk to use optimized vector operations when supported by your CPU")); + mVectorEnabled.setSelected(mPreference.isVectorEnabled()); + mVectorEnabled.selectedProperty() + .addListener((observable, oldValue, enabled) -> mPreference.setVectorEnabled(enabled)); + } + + return mVectorEnabled; + } + + private ToggleSwitch getHideDialogSwitch() + { + if(mHideDialogSwitch == null) + { + mHideDialogSwitch = new ToggleSwitch(); + mHideDialogSwitch.setTooltip(new Tooltip("Don't show dialog when new calibrations are available")); + mHideDialogSwitch.setSelected(mPreference.isHideCalibrationDialog()); + mHideDialogSwitch.selectedProperty().addListener((observable, oldValue, hide) -> mPreference.setHideCalibrationDialog(hide)); + } + + return mHideDialogSwitch; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java index 36f45629..2fae07c8 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.gui.preference.decoder; @@ -168,7 +167,7 @@ public class JmbeLibraryPreferenceEditor extends VBox */ private void checkForUpdatedLibrary() { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { try { Version current = mUserPreferences.getJmbeLibraryPreference().getCurrentVersion(); diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java b/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java index 868277b1..e64bad3b 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java +++ b/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java @@ -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 @@ -19,7 +19,8 @@ package io.github.dsheirer.gui.preference.playback; -import io.github.dsheirer.dsp.mixer.Oscillator; +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.ScalarRealOscillator; /** * Utility for generating tones for audio clips @@ -38,9 +39,9 @@ public class ToneUtil */ public static float[] getTone(ToneFrequency toneFrequency, ToneVolume toneVolume, int sampleCount) { - Oscillator oscillator = new Oscillator(toneFrequency.getValue(), 8000.0); + IRealOscillator oscillator = new ScalarRealOscillator(toneFrequency.getValue(), 8000.0); - float[] samples = oscillator.generateReal(sampleCount); + float[] samples = oscillator.generate(sampleCount); double gain = MAX_TONE_VOLUME * ((double)toneVolume.getValue() / 10.0); diff --git a/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java b/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java index 994731c4..fca4ffc4 100644 --- a/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java +++ b/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java @@ -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 @@ -125,7 +125,7 @@ public class JmbeCreator if(asset != null) { - ThreadPool.SCHEDULED.execute(() -> { + ThreadPool.CACHED.execute(() -> { Path tempDirectory = null; try { diff --git a/src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java b/src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java new file mode 100644 index 00000000..65e58a96 --- /dev/null +++ b/src/main/java/io/github/dsheirer/log/TextAreaLogAppender.java @@ -0,0 +1,50 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.log; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import javafx.application.Platform; +import javafx.scene.control.TextArea; + +/** + * Logback Log Appender that continuously writes to a JavaFX text area + * + * Note: this appender doesn't trim/truncate the text in the text area. + */ +public class TextAreaLogAppender extends AppenderBase +{ + private TextArea mTextArea; + + public TextAreaLogAppender(TextArea textArea, String name) + { + mTextArea = textArea; + setName(name); + } + + @Override + protected void append(ILoggingEvent eventObject) + { + Platform.runLater(() -> + { + mTextArea.appendText("\n" + eventObject.getMessage()); + }); + } +} diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 3ad78310..21a2534c 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -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 @@ -54,18 +54,15 @@ import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.module.decode.traffic.TrafficChannelManager; import io.github.dsheirer.module.log.EventLogger; import io.github.dsheirer.record.binary.BinaryRecorder; -import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.ComplexSamplesWaveRecorder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.IReusableBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableByteBufferListener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferListener; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ComplexSource; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; @@ -78,6 +75,7 @@ import io.github.dsheirer.source.heartbeat.IHeartbeatProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -105,9 +103,9 @@ public class ProcessingChain implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class); - private ReusableBufferBroadcaster mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster(); - private ReusableBufferBroadcaster mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster(); - private ReusableBufferBroadcaster mDemodulatedBitstreamBufferBroadcaster = new ReusableBufferBroadcaster(); + private Broadcaster mDemodulatedAudioBufferBroadcaster = new Broadcaster(); + private Broadcaster mBasebandComplexSamplesBroadcaster = new Broadcaster<>(); + private Broadcaster mDemodulatedBitstreamBufferBroadcaster = new Broadcaster(); private Broadcaster mAudioSegmentBroadcaster = new AudioSegmentBroadcaster<>(); private Broadcaster mDecodeEventBroadcaster = new Broadcaster<>(); private Broadcaster mChannelEventBroadcaster = new Broadcaster<>(); @@ -226,7 +224,7 @@ public class ProcessingChain implements Listener mAudioSegmentBroadcaster.dispose(); mDecodeEventBroadcaster.dispose(); mChannelEventBroadcaster.dispose(); - mBasebandComplexBufferBroadcaster.dispose(); + mBasebandComplexSamplesBroadcaster.dispose(); mDemodulatedBitstreamBufferBroadcaster.dispose(); mMessageBroadcaster.dispose(); mSquelchStateEventBroadcaster.dispose(); @@ -391,19 +389,19 @@ public class ProcessingChain implements Listener mMessageBroadcaster.addListener(((IMessageListener)module).getMessageListener()); } - if(module instanceof IReusableBufferListener) + if(module instanceof IRealBufferListener) { - mDemodulatedAudioBufferBroadcaster.addListener(((IReusableBufferListener)module).getReusableBufferListener()); + mDemodulatedAudioBufferBroadcaster.addListener(((IRealBufferListener)module).getBufferListener()); } - if(module instanceof IReusableByteBufferListener) + if(module instanceof IByteBufferListener) { - mDemodulatedBitstreamBufferBroadcaster.addListener(((IReusableByteBufferListener)module).getReusableByteBufferListener()); + mDemodulatedBitstreamBufferBroadcaster.addListener(((IByteBufferListener)module).getByteBufferListener()); } - if(module instanceof IReusableComplexBufferListener) + if(module instanceof IComplexSamplesListener) { - mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener()); + mBasebandComplexSamplesBroadcaster.addListener(((IComplexSamplesListener)module).getComplexSamplesListener()); } if(module instanceof ISourceEventListener) @@ -458,19 +456,19 @@ public class ProcessingChain implements Listener mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener()); } - if(module instanceof IReusableBufferListener) + if(module instanceof IRealBufferListener) { - mDemodulatedAudioBufferBroadcaster.removeListener(((IReusableBufferListener)module).getReusableBufferListener()); + mDemodulatedAudioBufferBroadcaster.removeListener(((IRealBufferListener)module).getBufferListener()); } - if(module instanceof IReusableByteBufferListener) + if(module instanceof IByteBufferListener) { - mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener)module).getReusableByteBufferListener()); + mDemodulatedBitstreamBufferBroadcaster.removeListener(((IByteBufferListener)module).getByteBufferListener()); } - if(module instanceof IReusableComplexBufferListener) + if(module instanceof IComplexSamplesListener) { - mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener()); + mBasebandComplexSamplesBroadcaster.removeListener(((IComplexSamplesListener)module).getComplexSamplesListener()); } if(module instanceof ISourceEventListener) @@ -535,14 +533,14 @@ public class ProcessingChain implements Listener ((IMessageProvider)module).setMessageListener(mMessageBroadcaster); } - if(module instanceof IReusableByteBufferProvider) + if(module instanceof IByteBufferProvider) { - ((IReusableByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster); + ((IByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster); } - if(module instanceof IReusableBufferProvider) + if(module instanceof IRealBufferProvider) { - ((IReusableBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster); + ((IRealBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster); } if(module instanceof ISourceEventProvider) @@ -572,9 +570,9 @@ public class ProcessingChain implements Listener ((IAudioSegmentProvider)module).setAudioSegmentListener(null); } - if(module instanceof IReusableByteBufferProvider) + if(module instanceof IByteBufferProvider) { - ((IReusableByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster); + ((IByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster); } if(module instanceof IDecodeEventProvider) @@ -607,9 +605,9 @@ public class ProcessingChain implements Listener ((IMessageProvider)module).setMessageListener(null); } - if(module instanceof IReusableBufferProvider) + if(module instanceof IRealBufferProvider) { - ((IReusableBufferProvider)module).setBufferListener(null); + ((IRealBufferProvider)module).setBufferListener(null); } if(module instanceof ISourceEventProvider) @@ -648,7 +646,7 @@ public class ProcessingChain implements Listener switch(mSource.getSampleType()) { case COMPLEX: - ((ComplexSource)mSource).setListener(mBasebandComplexBufferBroadcaster); + ((ComplexSource)mSource).setListener(mBasebandComplexSamplesBroadcaster); break; case REAL: ((RealSource)mSource).setListener(mDemodulatedAudioBufferBroadcaster); @@ -696,10 +694,10 @@ public class ProcessingChain implements Listener switch(mSource.getSampleType()) { case COMPLEX: - ((ComplexSource)mSource).removeListener(mBasebandComplexBufferBroadcaster); + ((ComplexSource)mSource).setListener(null); break; case REAL: - ((RealSource)mSource).removeListener(mDemodulatedAudioBufferBroadcaster); + ((RealSource)mSource).setListener(null); break; default: throw new IllegalArgumentException("Unrecognized source sample type - cannot start processing " + @@ -753,7 +751,7 @@ public class ProcessingChain implements Listener for(Module module : mModules) { - if(module instanceof ComplexBufferWaveRecorder) + if(module instanceof ComplexSamplesWaveRecorder) { recordingModules.add(module); } @@ -882,7 +880,7 @@ public class ProcessingChain implements Listener /** * Adds listener to receive demodulated audio buffers from an modules that produce demodulated audio. */ - public void addDemodulatedAudioListener(Listener listener) + public void addDemodulatedAudioListener(Listener listener) { mDemodulatedAudioBufferBroadcaster.addListener(listener); } @@ -890,7 +888,7 @@ public class ProcessingChain implements Listener /** * Removes the listener from receiving demodulated audio buffers. */ - public void removeDemodulatedAudioListener(Listener listener) + public void removeDemodulatedAudioListener(Listener listener) { mDemodulatedAudioBufferBroadcaster.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java b/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java index ffa4f6e1..d913d652 100644 --- a/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/afsk/AbstractAFSKDecoder.java @@ -1,25 +1,27 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.decode.afsk; import io.github.dsheirer.dsp.afsk.AFSK1200Decoder; import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; /** * Abstract class for Audio Frequency Shift Keying (AFSK) 1200-baud decoder based decoder modules. @@ -28,7 +30,7 @@ import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; * * Sub-class implementations should invoke getDecoder().setListener() to receive decoded symbol stream. */ -public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener +public abstract class AbstractAFSKDecoder extends Decoder implements IRealBufferListener, Listener { private AFSK1200Decoder mAFSK1200Decoder; @@ -48,13 +50,13 @@ public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBu } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - mAFSK1200Decoder.receive(reusableFloatBuffer); + mAFSK1200Decoder.receive(realBuffer); } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java index e225b0d5..32c3bf6e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java @@ -21,8 +21,9 @@ package io.github.dsheirer.module.decode.dmr; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; -import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2; -import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; @@ -35,16 +36,16 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @@ -52,7 +53,7 @@ import java.util.Map; * DMR decoder module. */ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableByteBufferProvider + IComplexSamplesListener, Listener, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(DMRDecoder.class); protected static final float SAMPLE_COUNTER_GAIN = 0.4f; @@ -61,15 +62,17 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, private Broadcaster mDibitBroadcaster = new Broadcaster<>(); private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300); private DMRMessageProcessor mMessageProcessor; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; + protected InterpolatingSampleBuffer mInterpolatingSampleBuffer; protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator; protected CostasLoop mCostasLoop; protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; protected DMRMessageFramer mMessageFramer; - private PowerMonitor mPowerMonitor = new PowerMonitor(); + protected PowerMonitor mPowerMonitor = new PowerMonitor(); /** * Constructs an instance @@ -101,7 +104,7 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, /** * Sets the sample rate and configures internal decoder components. - * @param sampleRate + * @param sampleRate of the channel to decode */ public void setSampleRate(double sampleRate) { @@ -113,7 +116,8 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, mPowerMonitor.setSampleRate((int)sampleRate); mSampleRate = sampleRate; - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); mFrequencyCorrectionSyncMonitor = new FrequencyCorrectionSyncMonitor(mCostasLoop, this); @@ -138,35 +142,21 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, /** * Primary method for processing incoming complex sample buffers - * @param reusableComplexBuffer containing channelized complex samples + * @param samples containing channelized complex samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process buffer for power measurements - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //User accounting of the incoming buffer is handled by the gain filter - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //User accounting of the incoming buffer is handled by the filter - return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -251,7 +241,7 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mByteBufferAssembler.setBufferListener(listener); } @@ -260,7 +250,7 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mByteBufferAssembler.removeBufferListener(listener); } @@ -318,21 +308,14 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, @Override public Listener getSourceEventListener() { - return new Listener() - { - @Override - public void receive(SourceEvent sourceEvent) - { - process(sourceEvent); - } - }; + return sourceEvent -> process(sourceEvent); } /** * Listener interface to receive reusable complex buffers */ @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return DMRDecoder.this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java index 40849285..1c7d1433 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.dmr; @@ -23,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; /** * Instrumented version of DMR decoder @@ -35,7 +34,7 @@ public class DMRDecoderInstrumented extends DMRDecoder private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -48,19 +47,27 @@ public class DMRDecoderInstrumented extends DMRDecoder } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for processing incoming complex sample buffers + * @param complexSamples containing channelized complex samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(new ComplexSamples(i, q)); } - return filtered; + //Process buffer for power measurements + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -104,7 +111,7 @@ public class DMRDecoderInstrumented extends DMRDecoder ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java index 0f9f6b65..ba7eb87c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java @@ -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 @@ -38,11 +38,11 @@ import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -173,20 +173,15 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene * * @param buffer to process into a stream of dibits for processing. */ - public void receive(ReusableByteBuffer buffer) + public void receive(ByteBuffer buffer) { - //Updates current timestamp to the timestamp from the incoming buffer - setCurrentTime(buffer.getTimestamp()); - - for(byte value : buffer.getBytes()) + for(byte value : buffer.array()) { for(int x = 0; x <= 3; x++) { receive(Dibit.parse(value, x)); } } - - buffer.decrementUserCount(); } /** @@ -469,7 +464,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } @@ -517,7 +512,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java index f05dea5c..81e434d0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.decode.fleetsync2; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -44,9 +46,9 @@ public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java index c92aeee6..bbee0e5d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.decode.lj1200; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -44,9 +46,9 @@ public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrum } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java index 2a546c92..182dcd8e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.decode.ltrnet; import io.github.dsheirer.bits.MessageFramer; @@ -22,10 +25,9 @@ import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; -public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener +public class LTRNetDecoder extends Decoder implements IRealBufferListener, Listener { public static final int LTR_NET_MESSAGE_LENGTH = 40; protected LTRDecoder mLTRDecoder; @@ -72,13 +74,13 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return mLTRDecoder; } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] reusableFloatBuffer) { mLTRDecoder.receive(reusableFloatBuffer); } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java index 2e0055d9..10a78c65 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java @@ -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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.decode.ltrnet; import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; public class LTRNetDecoderInstrumented extends LTRNetDecoder @@ -34,9 +36,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java index f4851940..b54e2cf9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.decode.ltrstandard; import io.github.dsheirer.alias.AliasList; @@ -23,10 +26,9 @@ import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; -public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener +public class LTRStandardDecoder extends Decoder implements IRealBufferListener, Listener { public static final int LTR_STANDARD_MESSAGE_LENGTH = 40; @@ -65,15 +67,15 @@ public class LTRStandardDecoder extends Decoder implements IReusableBufferListen } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return mLTRDecoder; } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - mLTRDecoder.receive(reusableFloatBuffer); + mLTRDecoder.receive(realBuffer); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java index 422c1745..3feab946 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java @@ -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,13 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.mdc1200; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -48,9 +46,9 @@ public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedA } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java index b9364e9f..b58a62a1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.mpt1327; @@ -26,15 +25,16 @@ import io.github.dsheirer.dsp.symbol.BinaryToByteBufferAssembler; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.afsk.AbstractAFSKDecoder; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; + /** * Instrumented version of the MPT1327 decoder. Exposes properties for instrumented manual decoding of a signal. */ -public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor, IReusableByteBufferProvider +public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor, IByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(MPT1327Decoder.class); @@ -114,13 +114,13 @@ public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbol } @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mBinaryToByteBufferAssembler.setBufferListener(listener); } @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mBinaryToByteBufferAssembler.removeBufferListener(listener); } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java index fa76b186..364bca80 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java @@ -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,13 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.mpt1327; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -48,9 +46,9 @@ public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstr } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java b/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java index 41316a41..4d789244 100644 --- a/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/nbfm/NBFMDecoder.java @@ -22,21 +22,21 @@ import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.IDecoderStateEventProvider; import io.github.dsheirer.channel.state.State; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory; -import io.github.dsheirer.dsp.filter.decimate.IComplexDecimationFilter; +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.complex.ComplexFIRFilter2; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; import io.github.dsheirer.dsp.filter.resample.RealResampler; -import io.github.dsheirer.dsp.fm.SquelchingFMDemodulator; +import io.github.dsheirer.dsp.fm.FmDemodulatorFactory; +import io.github.dsheirer.dsp.fm.ISquelchingFmDemodulator; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.PrimaryDecoder; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; @@ -47,22 +47,23 @@ import org.slf4j.LoggerFactory; * Decoder module with integrated narrowband FM (12.5 or 25.0 kHz channel) demodulator */ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableBufferProvider, + IComplexSamplesListener, Listener, IRealBufferProvider, IDecoderStateEventProvider { private final static Logger mLog = LoggerFactory.getLogger(NBFMDecoder.class); private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0; - private static final double POWER_SQUELCH_ALPHA_DECAY = 0.0004; - private static final double POWER_SQUELCH_THRESHOLD_DB = -78.0; + private static final float POWER_SQUELCH_ALPHA_DECAY = 0.0004f; + private static final float POWER_SQUELCH_THRESHOLD_DB = -78.0f; private static final int POWER_SQUELCH_RAMP = 4; - private ComplexFIRFilter2 mIQFilter; - private IComplexDecimationFilter mDecimationFilter; - private SquelchingFMDemodulator mDemodulator = new SquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, - POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private IRealDecimationFilter mIDecimationFilter; + private IRealDecimationFilter mQDecimationFilter; + private ISquelchingFmDemodulator mDemodulator; private RealResampler mResampler; private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor(); - private Listener mResampledReusableBufferListener; + private Listener mResampledBufferListener; private Listener mDecoderStateEventListener; private double mChannelBandwidth; private double mOutputSampleRate = DEMODULATED_AUDIO_SAMPLE_RATE; @@ -76,6 +77,8 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, { super( config ); mChannelBandwidth = config.getBandwidth().getValue(); + mDemodulator = FmDemodulatorFactory.getSquelchingFmDemodulator(POWER_SQUELCH_ALPHA_DECAY, + POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); mDemodulator.setSquelchThreshold(config.getSquelchThreshold()); } @@ -86,7 +89,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, } @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return this; } @@ -114,31 +117,33 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, } @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { - mResampledReusableBufferListener = listener; + mResampledBufferListener = listener; } @Override public void removeBufferListener() { - mResampledReusableBufferListener = null; + mResampledBufferListener = null; } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - if(mIQFilter == null) + if(mIDecimationFilter == null || mQDecimationFilter == null) { - reusableComplexBuffer.decrementUserCount(); throw new IllegalStateException("NBFM demodulator module must receive a sample rate change source " + "event before it can process complex sample buffers"); } - ReusableComplexBuffer decimatedBuffer = mDecimationFilter.decimate(reusableComplexBuffer); - ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(decimatedBuffer); + float[] decimatedI = mIDecimationFilter.decimateReal(samples.i()); + float[] decimatedQ = mQDecimationFilter.decimateReal(samples.q()); - ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer); + float[] filteredI = mIBasebandFilter.filter(decimatedI); + float[] filteredQ = mQBasebandFilter.filter(decimatedQ); + + float[] demodulated = mDemodulator.demodulate(filteredI, filteredQ); if(mResampler != null) { @@ -153,12 +158,11 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, //Either send the demodulated buffer to the resampler for distro, or decrement the user count if(mSquelch) { - demodulatedBuffer.incrementUserCount(); notifyIdle(); } else { - mResampler.resample(demodulatedBuffer); + mResampler.resample(demodulated); notifyCallContinuation(); } @@ -171,7 +175,6 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, } else { - demodulatedBuffer.decrementUserCount(); notifyIdle(); } } @@ -266,10 +269,10 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, { if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE) { - if(mIQFilter != null) + if(mIBasebandFilter != null) { - mIQFilter.dispose(); - mIQFilter = null; + mIBasebandFilter = null; + mQBasebandFilter = null; } double sampleRate = sourceEvent.getValue().doubleValue(); @@ -292,7 +295,8 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, decimatedSampleRate /= decimationRate; } - mDecimationFilter = DecimationFilterFactory.getComplexDecimationFilter(decimationRate); + mIDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimationRate); + mQDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimationRate); if((decimatedSampleRate < (2.0 * mChannelBandwidth))) { @@ -304,7 +308,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, int passBandStop = (int)(mChannelBandwidth * .8); int stopBandStart = (int)mChannelBandwidth; - float[] filterTaps = null; + float[] coefficients = null; FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() .sampleRate(decimatedSampleRate * 2) @@ -320,7 +324,7 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, try { - filterTaps = FilterFactory.getTaps(specification); + coefficients = FilterFactory.getTaps(specification); } catch(FilterDesignException fde) { @@ -329,28 +333,25 @@ public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, "] - will proceed using sinc (low-pass) filter"); } - if(filterTaps == null) + if(coefficients == null) { mLog.info("Unable to use remez filter designer for sample rate [" + decimatedSampleRate + "] pass band stop [" + passBandStop + "] and stop band start [" + stopBandStart + "] - will proceed using simple low pass filter design"); - filterTaps = FilterFactory.getLowPass(decimatedSampleRate, passBandStop, stopBandStart, 60, - Window.WindowType.HAMMING, true); + coefficients = FilterFactory.getLowPass(decimatedSampleRate, passBandStop, stopBandStart, 60, + WindowType.HAMMING, true); } - mIQFilter = new ComplexFIRFilter2(filterTaps); + mIBasebandFilter = FilterFactory.getRealFilter(coefficients); + mQBasebandFilter = FilterFactory.getRealFilter(coefficients); - mResampler = new RealResampler(decimatedSampleRate, mOutputSampleRate, 2000, 1000); + mResampler = new RealResampler(decimatedSampleRate, mOutputSampleRate, 4192, 512); - mResampler.setListener(reusableFloatBuffer -> + mResampler.setListener(resampled -> { - if(mResampledReusableBufferListener != null) + if(mResampledBufferListener != null) { - mResampledReusableBufferListener.receive(reusableFloatBuffer); - } - else - { - reusableFloatBuffer.decrementUserCount(); + mResampledBufferListener.receive(resampled); } }); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java deleted file mode 100644 index a3e46de5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/C4FMSymbolFilter.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * 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 - * ***************************************************************************** - */ - -package io.github.dsheirer.module.decode.p25.phase1; - -import io.github.dsheirer.dsp.gain.DirectGainControl; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.sample.real.RealSampleListener; -import io.github.dsheirer.sample.real.RealSampleProvider; -import io.github.dsheirer.source.ISourceEventListener; -import io.github.dsheirer.source.SourceEvent; -import org.apache.commons.math3.util.FastMath; - -public class C4FMSymbolFilter implements Listener, ISourceEventListener, RealSampleProvider -{ - private static final float TAPS[][] = - { - {0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f}, // 0/128 - {-1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f}, // 1/128 - {-3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f}, // 2/128 - {-4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f}, // 3/128 - {-6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f}, // 4/128 - {-7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f}, // 5/128 - {-9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f}, // 6/128 - {-1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f}, // 7/128 - {-1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f}, // 8/128 - {-1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f}, // 9/128 - {-1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f}, // 10/128 - {-1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f}, // 11/128 - {-1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f}, // 12/128 - {-1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f}, // 13/128 - {-2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f}, // 14/128 - {-2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f}, // 15/128 - {-2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f}, // 16/128 - {-2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f}, // 17/128 - {-2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f}, // 18/128 - {-2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f}, // 19/128 - {-3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f}, // 20/128 - {-3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f}, // 21/128 - {-3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f}, // 22/128 - {-3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f}, // 23/128 - {-3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f}, // 24/128 - {-3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f}, // 25/128 - {-3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f}, // 26/128 - {-3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f}, // 27/128 - {-4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f}, // 28/128 - {-4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f}, // 29/128 - {-4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f}, // 30/128 - {-4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f}, // 31/128 - {-4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f}, // 32/128 - {-4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f}, // 33/128 - {-4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f}, // 34/128 - {-4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f}, // 35/128 - {-5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f}, // 36/128 - {-5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f}, // 37/128 - {-5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f}, // 38/128 - {-5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f}, // 39/128 - {-5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f}, // 40/128 - {-5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f}, // 41/128 - {-5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f}, // 42/128 - {-5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f}, // 43/128 - {-5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f}, // 44/128 - {-5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f}, // 45/128 - {-5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f}, // 46/128 - {-6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f}, // 47/128 - {-6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f}, // 48/128 - {-6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f}, // 49/128 - {-6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f}, // 50/128 - {-6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f}, // 51/128 - {-6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f}, // 52/128 - {-6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f}, // 53/128 - {-6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f}, // 54/128 - {-6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f}, // 55/128 - {-6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f}, // 56/128 - {-6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f}, // 57/128 - {-6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f}, // 58/128 - {-6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f}, // 59/128 - {-6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f}, // 60/128 - {-6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f}, // 61/128 - {-6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f}, // 62/128 - {-6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f}, // 63/128 - {-6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f}, // 64/128 - {-6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f}, // 65/128 - {-6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f}, // 66/128 - {-6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f}, // 67/128 - {-6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f}, // 68/128 - {-6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f}, // 69/128 - {-6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f}, // 70/128 - {-6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f}, // 71/128 - {-6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f}, // 72/128 - {-6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f}, // 73/128 - {-6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f}, // 74/128 - {-6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f}, // 75/128 - {-6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f}, // 76/128 - {-6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f}, // 77/128 - {-6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f}, // 78/128 - {-6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f}, // 79/128 - {-6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f}, // 80/128 - {-6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f}, // 81/128 - {-6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f}, // 82/128 - {-6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f}, // 83/128 - {-6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f}, // 84/128 - {-6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f}, // 85/128 - {-6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f}, // 86/128 - {-6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f}, // 87/128 - {-5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f}, // 88/128 - {-5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f}, // 89/128 - {-5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f}, // 90/128 - {-5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f}, // 91/128 - {-5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f}, // 92/128 - {-5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f}, // 93/128 - {-5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f}, // 94/128 - {-5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f}, // 95/128 - {-5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f}, // 96/128 - {-5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f}, // 97/128 - {-4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f}, // 98/128 - {-4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f}, // 99/128 - {-4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f}, // 100/128 - {-4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f}, // 101/128 - {-4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f}, // 102/128 - {-4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f}, // 103/128 - {-4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f}, // 104/128 - {-4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f}, // 105/128 - {-3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f}, // 106/128 - {-3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f}, // 107/128 - {-3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f}, // 108/128 - {-3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f}, // 109/128 - {-3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f}, // 110/128 - {-3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f}, // 111/128 - {-2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f}, // 112/128 - {-2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f}, // 113/128 - {-2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f}, // 114/128 - {-2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f}, // 115/128 - {-2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f}, // 116/128 - {-2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f}, // 117/128 - {-1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f}, // 118/128 - {-1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f}, // 119/128 - {-1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f}, // 120/128 - {-1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f}, // 121/128 - {-1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f}, // 122/128 - {-9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f}, // 123/128 - {-7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f}, // 124/128 - {-5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f}, // 125/128 - {-3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f}, // 126/128 - {-1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f}, // 127/128 - {0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f}, // 128/128 - }; - - private static final int NUMBER_FILTER_TAPS = 8; - private static final int NUMBER_FILTER_STEPS = 128; - - private static final int SAMPLE_RATE = 48000; - private static final int SYMBOL_RATE = 4800; - - /* PLLGain loop gain constant */ - private static final double K_SYMBOL_SPREAD = 0.0100; - - /* Constraints on symbol spreading */ - private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20% - private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20% - - /* Symbol clock tracking loop gain */ - private static final double K_SYMBOL_TIMING = 0.025; - - /* Coarse and fine frequency tracking constants */ - private static final double K_COARSE_FREQUENCY = 0.00125; - private static final double K_FINE_FREQUENCY = 0.125; - - /* Frequency correction broadcast threshold */ -// private static final double COARSE_FREQUENCY_DEADBAND = 1.66; - private static final double COARSE_FREQUENCY_THRESHOLD = 1.20; - - /* 2.0 symbol spread gives -3, -1, 1, 3 */ - private float mSymbolSpread = 2.0f; - private float mSymbolClock = 0.0f; - private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE; - - private float mFineFrequencyCorrection = 0.0f; - private float mCoarseFrequencyCorrection = 0.0f; - - private float mHistory[] = new float[NUMBER_FILTER_TAPS]; - private int mHistoryLast = 0; - - private RealSampleListener mListener; - - private DirectGainControl mGainController = new DirectGainControl(15.0f, 0.1f, 35.0f, 0.3f); - - private int mFrequencyAdjustmentRequested = 0; - private int mFrequencyCorrection = 0; - private int mFrequencyCorrectionMaximum = 3000; - private boolean mResetFrequencyTracker = false; - private FrequencyCorrectionProcessor mFrequencyCorrectionProcessor; - private Listener mFrequencyChangeListener; - - /** - * C4FM Symbol Filter - * - * Sample Gain values - the gain of incoming sample values will critically - * impact the performance of this filter. Gain should be adjusted to - * optimize the value of mSymbolSpread toward a value of 2.0. If the value - * is less than 2.0, then increase gain. If the value is over 2.0, then - * decrease gain. - * - * When preceded by the RealAutomaticGainFilter class, an optimal gain - * setting for this filter is 15.4 and that yields a symbol spread that - * is centered on 2.0, ranging 1.92 to 2.08. - */ - public C4FMSymbolFilter(int frequencyCorrectionMaximum) - { - mFrequencyCorrectionMaximum = frequencyCorrectionMaximum; - } - - public void dispose() - { - mGainController = null; -// mFrequencyCorrectionControl = null; - mListener = null; - } - - @Override - public void receive(ReusableFloatBuffer buffer) - { - for(float sample : buffer.getSamples()) - { - receive(sample); - } - - /* If a frequency correction was requested during the processing of this - * buffer, we'll apply the change and it will be reflected in the next - * arriving buffer. Reset the lock on frequency correction and reset - * the internal frequency correction tracker */ - if(mFrequencyAdjustmentRequested != 0) - { - int correction = mFrequencyCorrection + mFrequencyAdjustmentRequested; - - if(correction > mFrequencyCorrectionMaximum) - { - correction = mFrequencyCorrectionMaximum; - } - else if(correction < -mFrequencyCorrectionMaximum) - { - correction = -mFrequencyCorrectionMaximum; - } - - broadcast(SourceEvent.channelFrequencyCorrectionRequest(correction)); - - mFrequencyAdjustmentRequested = 0; - } - } - - public void receive(float sample) - { - sample = mGainController.correct(sample); - - if(mResetFrequencyTracker) - { - mCoarseFrequencyCorrection = 0.0f; - mFineFrequencyCorrection = 0.0f; - - broadcast(SourceEvent.channelFrequencyCorrectionRequest(0l)); - - mResetFrequencyTracker = false; - } - - mSymbolClock += mSymbolTime; - - mHistory[mHistoryLast++] = sample; - - mHistoryLast %= NUMBER_FILTER_TAPS; - - if(mSymbolClock > 1.0f) - { - mSymbolClock -= 1.0f; - - int imu = (int) FastMath.floor(0.5 + - ((float)NUMBER_FILTER_STEPS * (mSymbolClock / mSymbolTime))); - - if(imu >= NUMBER_FILTER_STEPS) - { - imu = NUMBER_FILTER_STEPS - 1; - } - - int imu_p1 = imu + 1; - - int j = mHistoryLast; - - double interp = 0.0; - double interp_p1 = 0.0; - - for(int i = 0; i < NUMBER_FILTER_TAPS; i++) - { - interp += TAPS[imu][i] * mHistory[j]; - interp_p1 += TAPS[imu_p1][i] * mHistory[j]; - - j = (j + 1) % NUMBER_FILTER_TAPS; - } - - /* Output symbol will be interpolated value corrected for symbol - * spread and frequency offset */ - interp -= mFineFrequencyCorrection; - interp_p1 -= mFineFrequencyCorrection; - - /* Correct output for symbol deviation (spread) */ - float output = (float)(2.0 * interp / mSymbolSpread); - - /* Detect received symbol error: basically use a hard decision and - * subtract off expected position nominal symbol level which will be - * +/- 0.5 * symbol spread and +/- 1.5 symbol spread. Remember that - * nominal symbol spread will be 2.0 */ - double symbolError; - - if(interp < -mSymbolSpread) - { - /* symbol is -3: Expected at -1.5 * symbol spread */ - symbolError = interp + (1.5 * mSymbolSpread); - mSymbolSpread -= (symbolError * 0.5 * K_SYMBOL_SPREAD); - } - else if(interp < 0.0) - { - /* symbol is -1: Expected at -0.5 * symbol_spread */ - symbolError = interp + (0.5 * mSymbolSpread); - mSymbolSpread -= (symbolError * K_SYMBOL_SPREAD); - } - else if(interp < mSymbolSpread) - { - /* symbol is +1: Expected at +0.5 * symbol_spread */ - symbolError = interp - (0.5 * mSymbolSpread); - mSymbolSpread += (symbolError * K_SYMBOL_SPREAD); - } - else - { - /* symbol is +3: Expected at +1.5 * symbol_spread */ - symbolError = interp - (1.5 * mSymbolSpread); - mSymbolSpread += (symbolError * 0.5 * K_SYMBOL_SPREAD); - } - - /* Symbol clock tracking loop adjustment */ - if(interp_p1 < interp) - { - mSymbolClock += symbolError * K_SYMBOL_TIMING; - } - else - { - mSymbolClock -= symbolError * K_SYMBOL_TIMING; - } - - if(mSymbolSpread < SYMBOL_SPREAD_MIN) - { - mGainController.increase(); - - mSymbolSpread = SYMBOL_SPREAD_MIN; - } - else if(mSymbolSpread > SYMBOL_SPREAD_MAX) - { - mGainController.decrease(); - - mSymbolSpread = SYMBOL_SPREAD_MAX; - } - - mCoarseFrequencyCorrection += ((mFineFrequencyCorrection - - mCoarseFrequencyCorrection) * K_COARSE_FREQUENCY); - - mFineFrequencyCorrection += (symbolError * K_FINE_FREQUENCY); - - /* Queue a frequency adjustment (once per buffer) as needed */ - if(FastMath.abs(mCoarseFrequencyCorrection) > COARSE_FREQUENCY_THRESHOLD) - { - mFrequencyAdjustmentRequested = - 500 * (mCoarseFrequencyCorrection > 0 ? 1 : -1); - } - - /* dispatch the interpolated value to the listener */ - if(mListener != null) - { - mListener.receive(output); - } - } - } - - @Override - public void setListener(RealSampleListener listener) - { - mListener = listener; - } - - @Override - public void removeListener(RealSampleListener listener) - { - mListener = null; - } - - /** - * Broadcasts a frequency change event to the registered listener - * - * @param event - */ - public void broadcast(SourceEvent event) - { - if(mFrequencyChangeListener != null) - { - mFrequencyChangeListener.receive(event); - } - } - - /** - * Sets the listener for frequency change events. - * - * @param listener - */ - public void setFrequencyChangeListener(Listener listener) - { - mFrequencyChangeListener = listener; - } - - @Override - public Listener getSourceEventListener() - { - if(mFrequencyCorrectionProcessor == null) - { - mFrequencyCorrectionProcessor = new FrequencyCorrectionProcessor(); - } - - return mFrequencyCorrectionProcessor; - } - - /** - * Receives notifications that the channel frequency correction has been - * applied and updates internal tracking value. - */ - public class FrequencyCorrectionProcessor implements Listener - { - @Override - public void receive(SourceEvent event) - { - switch(event.getEvent()) - { - case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE: - mFrequencyCorrection = event.getValue().intValue(); - - //Reset internal frequency tracking - mCoarseFrequencyCorrection = 0.0f; - mFineFrequencyCorrection = 0.0f; - break; - case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE: - case NOTIFICATION_SAMPLE_RATE_CHANGE: - mResetFrequencyTracker = true; - break; - default: - break; - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java index d68298f2..24885908 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java @@ -25,16 +25,17 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; +import java.nio.ByteBuffer; + public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableByteBufferProvider + IComplexSamplesListener, Listener, IByteBufferProvider { private double mSampleRate; private Broadcaster mDibitBroadcaster = new Broadcaster<>(); @@ -78,7 +79,7 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mByteBufferAssembler.setBufferListener(listener); } @@ -87,7 +88,7 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mByteBufferAssembler.removeBufferListener(listener); } @@ -160,7 +161,7 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve * Listener interface to receive reusable complex buffers */ @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return P25P1Decoder.this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java index d38ad8e9..de5d4af9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FM.java @@ -24,16 +24,16 @@ package io.github.dsheirer.module.decode.p25.phase1; 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.complex.ComplexFIRFilter2; -import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor; import io.github.dsheirer.dsp.psk.pll.PLLBandwidth; -import io.github.dsheirer.dsp.squelch.PowerMonitor; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,9 +51,10 @@ public class P25P1DecoderC4FM extends P25P1Decoder protected CostasLoop mCostasLoop; protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; protected P25P1MessageFramer mMessageFramer; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; /** * P25 Phase 1 - standard C4FM modulation decoder. Uses Differential QPSK decoding with a Costas PLL and a @@ -68,7 +69,8 @@ public class P25P1DecoderC4FM extends P25P1Decoder public void setSampleRate(double sampleRate) { super.setSampleRate(sampleRate); - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); @@ -95,35 +97,21 @@ public class P25P1DecoderC4FM extends P25P1Decoder /** * Primary method for processing incoming complex sample buffers - * @param reusableComplexBuffer containing channelized complex samples + * @param samples containing channelized complex samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process the buffer for power meter measurements (before gain is applied) - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //User accounting of the incoming buffer is handled by the gain filter - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //User accounting of the incoming buffer is handled by the filter - return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java index 8b7438c9..6101314b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderC4FMInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.p25.phase1; @@ -23,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM { @@ -32,7 +31,7 @@ public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -44,19 +43,27 @@ public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for processing incoming complex sample buffers + * @param samples containing channelized complex samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(new ComplexSamples(i, q)); } - return filtered; + //Process the buffer for power meter measurements (before gain is applied) + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -100,7 +107,7 @@ public class P25P1DecoderC4FMInstrumented extends P25P1DecoderC4FM ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java index e37b1746..6db8f44f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSM.java @@ -22,26 +22,21 @@ package io.github.dsheirer.module.decode.p25.phase1; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2; -import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor; import io.github.dsheirer.dsp.psk.pll.PLLBandwidth; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.protocol.Protocol; -import io.github.dsheirer.record.binary.BinaryRecorder; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.source.wave.ComplexWaveSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -52,8 +47,9 @@ public class P25P1DecoderLSM extends P25P1Decoder protected static final float SAMPLE_COUNTER_GAIN = 0.3f; private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); protected DQPSKGardnerDemodulator mQPSKDemodulator; protected P25P1MessageFramer mMessageFramer; protected CostasLoop mCostasLoop; @@ -79,7 +75,8 @@ public class P25P1DecoderLSM extends P25P1Decoder { super.setSampleRate(sampleRate); - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_200); @@ -108,37 +105,19 @@ public class P25P1DecoderLSM extends P25P1Decoder * Primary method for receiving incoming channel samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + //The filter will decrement the user count when finished - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process the buffer for power measurements - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //AGC will decrement the user count when finished - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //Decoder will decrement the user count when finished - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //No additional filtering of the channel is currently needed, since the polyphase channelizer - //provides the filtering. - return reusableComplexBuffer; - - //User accounting of the incoming buffer is handled by the filter -// return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -194,44 +173,4 @@ public class P25P1DecoderLSM extends P25P1Decoder return filter; } - - public static void main(String[] args) - { - String path = "/media/denny/500G1EXT4/RadioRecordings/"; -// String input = "DFWAirport_Site_857_3875_baseband_20181213_223236.wav"; -// String output = "DFWAirport_Site_857_3875_baseband_20181213_223236"; - - String input = "P25P2_HCPM_Metrocrest_Dallas_857_7625_phase_2_not_motorola_baseband_20181213_224616_control_channel.wav"; - String output = "P25P2_HCPM_Metrocrest_Dallas_857_7625_phase_2_not_motorola_baseband_20181213_224616_control_channel"; - - - P25P1DecoderLSM decoder = new P25P1DecoderLSM(); - BinaryRecorder recorder = new BinaryRecorder(Path.of(path), output, Protocol.APCO25); - decoder.setBufferListener(recorder.getReusableByteBufferListener()); - recorder.start(); - - File file = new File(path + input); - - boolean running = true; - - try(ComplexWaveSource source = new ComplexWaveSource(file)) - { - decoder.setSampleRate(50000.0); - source.setListener(decoder); - source.start(); - - while(running) - { - source.next(200, true); - } - } - catch(IOException e) - { - mLog.error("Error", e); - running = false; - } - - recorder.stop(); - } - } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java index 0c4ad986..2cf8b055 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderLSMInstrumented.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.p25.phase1; @@ -23,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM { @@ -32,7 +31,7 @@ public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -46,19 +45,27 @@ public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for receiving incoming channel samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + //The filter will decrement the user count when finished + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(new ComplexSamples(i, q)); } - return filtered; + //Process the buffer for power measurements + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -101,7 +108,7 @@ public class P25P1DecoderLSMInstrumented extends P25P1DecoderLSM ((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java index 4fef8d91..25ac11b8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java @@ -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 @@ -36,7 +36,6 @@ import io.github.dsheirer.message.StuffBitsMessage; import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.ip.IPacket; import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket; import io.github.dsheirer.module.decode.ip.udp.UDPPacket; @@ -53,17 +52,16 @@ import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.function.Consumer; /** @@ -376,20 +374,15 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect * * @param buffer to process into a stream of dibits for processing. */ - public void receive(ReusableByteBuffer buffer) + public void receive(ByteBuffer buffer) { - //Updates current timestamp to the timestamp from the incoming buffer - setCurrentTime(buffer.getTimestamp()); - - for(byte value : buffer.getBytes()) + for(byte value : buffer.array()) { for(int x = 0; x <= 3; x++) { receive(Dibit.parse(value, x)); } } - - buffer.decrementUserCount(); } @Override @@ -545,7 +538,7 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java index 0bafa507..7ee604f8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java @@ -25,19 +25,20 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferProvider; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; +import java.nio.ByteBuffer; + /** * Base P25 Phase 2 Decoder */ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableByteBufferProvider + IComplexSamplesListener, Listener, IByteBufferProvider { private double mSampleRate; private Broadcaster mDibitBroadcaster = new Broadcaster<>(); @@ -80,7 +81,7 @@ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mByteBufferAssembler.setBufferListener(listener); } @@ -89,7 +90,7 @@ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEve * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mByteBufferAssembler.removeBufferListener(listener); } @@ -163,7 +164,7 @@ public abstract class P25P2Decoder extends FeedbackDecoder implements ISourceEve * Listener interface to receive reusable complex buffers */ @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return P25P2Decoder.this; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java index 3dfc5f33..1cf97217 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java @@ -24,32 +24,25 @@ package io.github.dsheirer.module.decode.p25.phase2; 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.complex.ComplexFIRFilter2; -import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; +import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor; import io.github.dsheirer.dsp.psk.pll.PLLBandwidth; -import io.github.dsheirer.dsp.squelch.PowerMonitor; import io.github.dsheirer.identifier.Form; import io.github.dsheirer.identifier.IdentifierUpdateListener; import io.github.dsheirer.identifier.IdentifierUpdateNotification; -import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; -import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.source.wave.ComplexWaveSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -64,9 +57,10 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate protected DQPSKGardnerDemodulator mQPSKDemodulator; protected CostasLoop mCostasLoop; protected P25P2MessageFramer mMessageFramer; - private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); + protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); private Map mBasebandFilters = new HashMap<>(); - private ComplexFIRFilter2 mBasebandFilter; + protected IRealFilter mIBasebandFilter; + protected IRealFilter mQBasebandFilter; private DecodeConfigP25Phase2 mDecodeConfigP25Phase2; private FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; @@ -81,7 +75,8 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate { super.setSampleRate(sampleRate); - mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter()); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); @@ -113,35 +108,21 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate /** * Primary method for processing incoming complex sample buffers - * @param reusableComplexBuffer containing channelized complex samples + * @param samples containing channelized complex samples */ @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); //Process the buffer for power measurements - mPowerMonitor.process(basebandFiltered); + mPowerMonitor.process(i, q); - //User accounting of the incoming buffer is handled by the gain filter - ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); - - mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); - - //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(gainApplied); - } - - /** - * Filters the complex buffer and returns a new reusable complex buffer with the filtered contents. - * @param reusableComplexBuffer to filter - * @return filtered complex buffer - */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) - { - //User accounting of the incoming buffer is handled by the filter - return mBasebandFilter.filter(reusableComplexBuffer); + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -224,83 +205,6 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate } } - public static class MessageErrorListener implements Listener - { - private int mMessageCount = 0; - private int mBitErrorCount = 0; - - @Override - public void receive(IMessage iMessage) - { - if(!(iMessage instanceof SyncLossMessage) && !(iMessage instanceof EncryptionSynchronizationSequence)) - { - mMessageCount++; - } - - if(iMessage instanceof MacMessage) - { - mBitErrorCount += ((MacMessage)iMessage).getBitErrorCount(); - } - - mLog.debug("[" + mMessageCount + " | " + mBitErrorCount + "] " + iMessage.toString()); - } - } - - public static void main(String[] args) - { -// String path = "/media/denny/500G1EXT4/RadioRecordings/APCO25P2/CNYICC/"; -// String input = "CNYICC_Rome_CNYICC_154_250_baseband_20190322_180331_good.wav"; -// String output = "CNYICC_ROME_154_250_8"; - - String path = "/home/denny/SDRTrunk/recordings/"; - String input = "CNYICC_Site_P25P2_151.37_baseband_20200221_110306.wav"; - String output = "P25P2_test"; - - ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 686, 677); //CNYICC - DecodeConfigP25Phase2 decodeConfigP25Phase2 = new DecodeConfigP25Phase2(); - decodeConfigP25Phase2.setScrambleParameters(scrambleParameters); - - P25P2DecoderHDQPSK decoder = new P25P2DecoderHDQPSK(decodeConfigP25Phase2); - decoder.setMessageListener(new MessageErrorListener()); - -// decoder.setMessageListener(new Listener() -// { -// @Override -// public void receive(IMessage iMessage) -// { -// mLog.debug(iMessage.toString()); -// } -// }); - decoder.start(); - -// BinaryRecorder recorder = new BinaryRecorder(Path.of(path), output, Protocol.APCO25_PHASE2); -// decoder.setBufferListener(recorder.getReusableByteBufferListener()); -// recorder.start(); - - File file = new File(path + input); - - boolean running = true; - - try(ComplexWaveSource source = new ComplexWaveSource(file)) - { - decoder.setSampleRate(50000.0); - source.setListener(decoder); - source.start(); - - while(running) - { - source.next(200, true); - } - } - catch(IOException e) - { - mLog.error("Error", e); - running = false; - } - -// recorder.stop(); - } - @Override public Listener getIdentifierUpdateListener() { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java index f5228020..d6f53e92 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.module.decode.p25.phase2; @@ -25,8 +22,8 @@ import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK { @@ -34,7 +31,7 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK private Listener mPLLFrequencyListener; private Listener mSamplesPerSymbolListener; private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; + private Listener mFilteredSymbolListener; private Listener mSymbolDecisionDataListener; /** @@ -55,19 +52,27 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK } /** - * Overrides the filter method so that we can capture the filtered samples for instrumentation + * Primary method for processing incoming complex sample buffers + * @param samples containing channelized complex samples */ - protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer) + @Override + public void receive(ComplexSamples samples) { - ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); + mMessageFramer.setCurrentTime(System.currentTimeMillis()); + + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); if(mFilteredSymbolListener != null) { - filtered.incrementUserCount(); - mFilteredSymbolListener.receive(filtered); + mFilteredSymbolListener.receive(samples); } - return filtered; + //Process the buffer for power measurements + mPowerMonitor.process(i, q); + + ComplexSamples amplified = mAGC.process(i, q); + mQPSKDemodulator.receive(amplified); } /** @@ -119,7 +124,7 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK getDemodulator().setPLLFrequencyListener(listener); } - public void setFilteredBufferListener(Listener listener) + public void setFilteredBufferListener(Listener listener) { mFilteredSymbolListener = listener; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java index 8a1dbdf9..fc771dab 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java @@ -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 @@ -38,12 +38,12 @@ import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -190,20 +190,15 @@ public class P25P2MessageFramer implements Listener * * @param buffer to process into a stream of dibits for processing. */ - public void receive(ReusableByteBuffer buffer) + public void receive(ByteBuffer buffer) { - //TODO: set timestamp in super frame detector - setCurrentTime(buffer.getTimestamp()); - - for(byte value : buffer.getBytes()) + for(byte value : buffer.array()) { for(int x = 0; x <= 3; x++) { receive(Dibit.parse(value, x)); } } - - buffer.decrementUserCount(); } public static void main(String[] args) @@ -298,7 +293,7 @@ public class P25P2MessageFramer implements Listener { while(reader.hasNext()) { - ReusableByteBuffer buffer = reader.next(); + ByteBuffer buffer = reader.next(); messageFramer.receive(buffer); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java index d9336b5a..6fd0a2bf 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java @@ -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 - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.passport; @@ -26,10 +25,9 @@ import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferListener; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.real.IRealBufferListener; -public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener +public class PassportDecoder extends Decoder implements IRealBufferListener, Listener { public static final int PASSPORT_MESSAGE_LENGTH = 68; @@ -55,15 +53,15 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener, } @Override - public Listener getReusableBufferListener() + public Listener getBufferListener() { return mLTRDecoder; } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - mLTRDecoder.receive(reusableFloatBuffer); + mLTRDecoder.receive(realBuffer); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java index a9cfd979..0c1d10ee 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java @@ -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,13 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.tait; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; /** @@ -48,9 +46,9 @@ public class Tait1200DecoderInstrumented extends Tait1200Decoder implements IIns } @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + public void receive(float[] realBuffer) { - super.receive(reusableFloatBuffer); + super.receive(realBuffer); bufferCount.setValue(bufferCount.intValue() + 1); } diff --git a/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java index 560a9c66..cd400b5c 100644 --- a/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java +++ b/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java @@ -1,36 +1,36 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.demodulate.am; import io.github.dsheirer.dsp.am.AMDemodulator; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.Window.WindowType; 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.real.RealFIRFilter2; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; import io.github.dsheirer.dsp.filter.resample.RealResampler; import io.github.dsheirer.dsp.gain.AutomaticGainControl; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; @@ -39,23 +39,24 @@ import org.slf4j.LoggerFactory; /** * AM Demodulator Module for demodulating complex baseband channel sample buffers and producing demodulated audio * at a specified output sample rate, normally 8 kHz. - * + *

* This module requires a sample rate SourceEvent prior to processing baseband sample buffers in order to configure * internal filters and the resampler. */ -public class AMDemodulatorModule extends Module implements ISourceEventListener, IReusableComplexBufferListener, - IReusableBufferProvider, Listener +public class AMDemodulatorModule extends Module + implements ISourceEventListener, IComplexSamplesListener, IRealBufferProvider, Listener { private final static Logger mLog = LoggerFactory.getLogger(AMDemodulatorModule.class); - private static float[] mLowPassFilterTaps; - private ComplexFIRFilter2 mIQFilter; + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private AMDemodulator mDemodulator; - private RealFIRFilter2 mLowPassFilter; + private IRealFilter mLowPassFilter; private AutomaticGainControl mAGC = new AutomaticGainControl(); private double mChannelBandwidth; private double mOutputSampleRate; private RealResampler mResampler; - private Listener mResampledReusableBufferListener; + private Listener mResampledReusableBufferListener; private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor(); /** @@ -71,101 +72,81 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener, mDemodulator = new AMDemodulator(500.0f); - if(mLowPassFilterTaps == null) + float[] lowPassCoefficients = null; + + FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder().sampleRate(mOutputSampleRate) + .gridDensity(16).oddLength(true).passBandCutoff(3000).passBandAmplitude(1.0).passBandRipple(0.01) + .stopBandStart(3500).stopBandAmplitude(0.0).stopBandRipple(0.028).build(); + + try { - FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate(mOutputSampleRate) - .gridDensity(16) - .oddLength(true) - .passBandCutoff(3000) - .passBandAmplitude(1.0) - .passBandRipple(0.01) - .stopBandStart(3500) - .stopBandAmplitude(0.0) - .stopBandRipple(0.028) - .build(); - - try - { - mLowPassFilterTaps = FilterFactory.getTaps(specification); - } - catch(FilterDesignException fde) - { - mLog.error("Couldn't design AM demodulator remez filter for sample rate [" + mOutputSampleRate + - "] pass frequency [3000] and stop frequency [3500] - using sinc filter"); - } - - if(mLowPassFilterTaps == null) - { - mLowPassFilterTaps = FilterFactory.getLowPass(mOutputSampleRate, 3000, 3500, - 60, WindowType.HAMMING, true); - } + lowPassCoefficients = FilterFactory.getTaps(specification); + } + catch(FilterDesignException fde) + { + mLog.error("Couldn't design AM demodulator remez filter for sample rate [" + mOutputSampleRate + + "] pass frequency [3000] and stop frequency [3500] - using sinc filter"); } - mLowPassFilter = new RealFIRFilter2(mLowPassFilterTaps); + if(lowPassCoefficients == null) + { + lowPassCoefficients = FilterFactory.getLowPass(mOutputSampleRate, 3000, 3500, 60, WindowType.HAMMING, true); + } + + mLowPassFilter = FilterFactory.getRealFilter(lowPassCoefficients); } - @Override - public void receive(ReusableComplexBuffer basebandBuffer) + @Override public void receive(ComplexSamples samples) { - ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer); - ReusableFloatBuffer demodulated = mDemodulator.demodulate(filteredBuffer); + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); + float[] demodulated = mDemodulator.demodulate(i, q); mResampler.resample(demodulated); } /** * Processes resampled audio buffers produced by the resampler. + * * @param resampledAudio buffer to process. */ - private void processResampledAudio(ReusableFloatBuffer resampledAudio) + private void processResampledAudio(float[] resampledAudio) { if(mResampledReusableBufferListener != null) { - ReusableFloatBuffer filteredAudio = mLowPassFilter.filter(resampledAudio); - ReusableFloatBuffer gainApplied = mAGC.process(filteredAudio); - mResampledReusableBufferListener.receive(gainApplied); - } - else - { - resampledAudio.decrementUserCount(); + float[] filtered = mLowPassFilter.filter(resampledAudio); + float[] amplified = mAGC.process(filtered); + mResampledReusableBufferListener.receive(amplified); } } - @Override - public Listener getReusableComplexBufferListener() + @Override public Listener getComplexSamplesListener() { return this; } - @Override - public void reset() + @Override public void reset() { } - @Override - public void start() + @Override public void start() { } - @Override - public void stop() + @Override public void stop() { } - @Override - public void setBufferListener(Listener listener) + @Override public void setBufferListener(Listener listener) { mResampledReusableBufferListener = listener; } - @Override - public void removeBufferListener() + @Override public void removeBufferListener() { mResampledReusableBufferListener = null; } - @Override - public Listener getSourceEventListener() + @Override public Listener getSourceEventListener() { return mSourceEventProcessor; } @@ -175,73 +156,58 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener, */ public class SourceEventProcessor implements Listener { - @Override - public void receive(SourceEvent sourceEvent) + @Override public void receive(SourceEvent sourceEvent) { if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE) { - if(mIQFilter != null) + if(mIBasebandFilter != null || mQBasebandFilter != null) { - mIQFilter.dispose(); - mIQFilter = null; + mIBasebandFilter = null; + mQBasebandFilter = null; } double sampleRate = sourceEvent.getValue().doubleValue(); if((sampleRate < (2.0 * mChannelBandwidth))) { - throw new IllegalStateException("AM Demodulator with channel bandwidth [" + mChannelBandwidth + - "] requires a channel sample rate of [" + (2.0 * mChannelBandwidth + "] - sample rate of [" + - sampleRate + "] is not supported")); + throw new IllegalStateException("AM Demodulator with channel bandwidth [" + mChannelBandwidth + + "] requires a channel sample rate of [" + (2.0 * mChannelBandwidth + + "] - sample rate of [" + sampleRate + "] is not supported")); } double cutoff = sampleRate / 4.0; int passBandStop = (int)cutoff - 500; int stopBandStart = (int)cutoff + 500; - float[] filterTaps = null; + float[] coefficients = null; - FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate(sampleRate) - .gridDensity(16) - .oddLength(true) - .passBandCutoff(passBandStop) - .passBandAmplitude(1.0) - .passBandRipple(0.01) - .stopBandStart(stopBandStart) - .stopBandAmplitude(0.0) - .stopBandRipple(0.028) //Approximately 60 dB attenuation - .build(); + FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder().sampleRate(sampleRate) + .gridDensity(16).oddLength(true).passBandCutoff(passBandStop).passBandAmplitude(1.0) + .passBandRipple(0.01).stopBandStart(stopBandStart).stopBandAmplitude(0.0) + .stopBandRipple(0.028) //Approximately 60 dB attenuation + .build(); try { - filterTaps = FilterFactory.getTaps(specification); + coefficients = FilterFactory.getTaps(specification); } catch(FilterDesignException fde) { - mLog.error("Couldn't design FM demodulator remez filter for sample rate [" + sampleRate + - "] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart + - "] - using sinc filter"); + mLog.error("Couldn't design AM demodulator remez filter for sample rate [" + sampleRate + + "] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart + + "] - using sinc filter"); } - if(filterTaps == null) + if(coefficients == null) { - filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, - Window.WindowType.HAMMING, true); + coefficients = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, + WindowType.HAMMING, true); } - mIQFilter = new ComplexFIRFilter2(filterTaps); - - mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000); - - mResampler.setListener(new Listener() - { - @Override - public void receive(ReusableFloatBuffer resampledAudioBuffer) - { - processResampledAudio(resampledAudioBuffer); - } - }); + mIBasebandFilter = FilterFactory.getRealFilter(coefficients); + mQBasebandFilter = FilterFactory.getRealFilter(coefficients); + mResampler = new RealResampler(sampleRate, mOutputSampleRate, 8192, 512); + mResampler.setListener(resampledBuffer -> processResampledAudio(resampledBuffer)); } } } diff --git a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java index a51e44ee..0ba78a80 100644 --- a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java +++ b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java @@ -1,34 +1,36 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.module.demodulate.fm; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.Window; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; -import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2; +import io.github.dsheirer.dsp.filter.fir.real.IRealFilter; import io.github.dsheirer.dsp.filter.resample.RealResampler; -import io.github.dsheirer.dsp.fm.FMDemodulator; +import io.github.dsheirer.dsp.fm.ScalarFMDemodulator; import io.github.dsheirer.dsp.squelch.PowerMonitor; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableBufferProvider; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; +import io.github.dsheirer.sample.real.IRealBufferProvider; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; @@ -41,16 +43,17 @@ import org.slf4j.LoggerFactory; * Note: no filtering is applied to the demodulated audio. */ public class FMDemodulatorModule extends Module implements ISourceEventListener, ISourceEventProvider, - IReusableComplexBufferListener, Listener, IReusableBufferProvider + IComplexSamplesListener, Listener, IRealBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatorModule.class); - private ComplexFIRFilter2 mIQFilter; - private FMDemodulator mDemodulator = new FMDemodulator(); + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private ScalarFMDemodulator mDemodulator = new ScalarFMDemodulator(); private PowerMonitor mPowerMonitor = new PowerMonitor(); private RealResampler mResampler; private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor(); - private Listener mResampledReusableBufferListener; + private Listener mResampledBufferListener; private double mChannelBandwidth; private double mOutputSampleRate; @@ -68,7 +71,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, } @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return this; } @@ -82,12 +85,6 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, @Override public void dispose() { - if(mIQFilter != null) - { - mIQFilter.dispose(); - mIQFilter = null; - } - mDemodulator.dispose(); mDemodulator = null; } @@ -109,38 +106,35 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, } @Override - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { - mResampledReusableBufferListener = listener; + mResampledBufferListener = listener; } @Override public void removeBufferListener() { - mResampledReusableBufferListener = null; + mResampledBufferListener = null; } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(ComplexSamples samples) { - if(mIQFilter == null) + if(mIBasebandFilter == null || mQBasebandFilter == null) { - reusableComplexBuffer.decrementUserCount(); throw new IllegalStateException("FM demodulator module must receive a sample rate change source " + "event before it can process complex sample buffers"); } - ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(reusableComplexBuffer); - mPowerMonitor.process(basebandFilteredBuffer); - ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer); + float[] i = mIBasebandFilter.filter(samples.i()); + float[] q = mQBasebandFilter.filter(samples.q()); + + mPowerMonitor.process(i, q); + float[] demodulated = mDemodulator.demodulate(i, q); if(mResampler != null) { - mResampler.resample(demodulatedBuffer); - } - else - { - demodulatedBuffer.decrementUserCount(); + mResampler.resample(demodulated); } } @@ -166,12 +160,6 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, { if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE) { - if(mIQFilter != null) - { - mIQFilter.dispose(); - mIQFilter = null; - } - double sampleRate = sourceEvent.getValue().doubleValue(); if((sampleRate < (2.0 * mChannelBandwidth))) @@ -187,7 +175,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, int passBandStop = (int)cutoff - 500; int stopBandStart = (int)cutoff + 500; - float[] filterTaps = null; + float[] coefficients = null; FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() .sampleRate(sampleRate) @@ -203,7 +191,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, try { - filterTaps = FilterFactory.getTaps(specification); + coefficients = FilterFactory.getTaps(specification); } catch(FilterDesignException fde) { @@ -212,29 +200,20 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, "] - using sinc filter"); } - if(filterTaps == null) + if(coefficients == null) { - filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, - Window.WindowType.HAMMING, true); + coefficients = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60, + WindowType.HAMMING, true); } - mIQFilter = new ComplexFIRFilter2(filterTaps); + mIBasebandFilter = FilterFactory.getRealFilter(coefficients); + mQBasebandFilter = FilterFactory.getRealFilter(coefficients); + mResampler = new RealResampler(sampleRate, mOutputSampleRate, 8192, 512); - mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000); - - mResampler.setListener(new Listener() - { - @Override - public void receive(ReusableFloatBuffer reusableFloatBuffer) + mResampler.setListener(resampledBuffer -> { + if(mResampledBufferListener != null) { - if(mResampledReusableBufferListener != null) - { - mResampledReusableBufferListener.receive(reusableFloatBuffer); - } - else - { - reusableFloatBuffer.decrementUserCount(); - } + mResampledBufferListener.receive(resampledBuffer); } }); } diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java index 0e00518f..d9309dea 100644 --- a/src/main/java/io/github/dsheirer/preference/PreferenceType.java +++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java @@ -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 @@ -24,6 +24,7 @@ package io.github.dsheirer.preference; */ public enum PreferenceType { + CALIBRATION, DECODE_EVENT, DIRECTORY, DUPLICATE_CALL_DETECTION, diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java index b301c98e..bc247a07 100644 --- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java +++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java @@ -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,6 +20,7 @@ package io.github.dsheirer.preference; import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; import io.github.dsheirer.preference.decoder.JmbeLibraryPreference; import io.github.dsheirer.preference.directory.DirectoryPreference; import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference; @@ -53,6 +54,7 @@ import io.github.dsheirer.sample.Listener; */ public class UserPreferences implements Listener { + private VectorCalibrationPreference mVectorCalibrationPreference; private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference; private DecodeEventPreference mDecodeEventPreference; private DirectoryPreference mDirectoryPreference; @@ -76,6 +78,11 @@ public class UserPreferences implements Listener loadPreferenceTypes(); } + public VectorCalibrationPreference getVectorCalibrationPreference() + { + return mVectorCalibrationPreference; + } + /** * Java FX window management preferences */ @@ -186,6 +193,7 @@ public class UserPreferences implements Listener */ private void loadPreferenceTypes() { + mVectorCalibrationPreference = new VectorCalibrationPreference(this::receive); mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this::receive); mDecodeEventPreference = new DecodeEventPreference(this::receive); mDirectoryPreference = new DirectoryPreference(this::receive); diff --git a/src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java b/src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java new file mode 100644 index 00000000..83a0ce38 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/calibration/VectorCalibrationPreference.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.preference.calibration; + +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.prefs.Preferences; + +/** + * User preferences for CPU calibration for scalar vs vector SIMD implementations + */ +public class VectorCalibrationPreference extends Preference +{ + private static final String PREFERENCE_KEY_HIDE_CALIBRATION_DIALOG = "hide.calibration.dialog"; + private static final String PREFERENCE_KEY_VECTOR_ENABLED = "vector.enabled"; + + private final static Logger mLog = LoggerFactory.getLogger(VectorCalibrationPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(VectorCalibrationPreference.class); + private Boolean mVectorEnabled; + private Boolean mHideCalibrationDialog; + + /** + * Constructs an instance + * @param updateListener to receive notifications that a preference has been updated + */ + public VectorCalibrationPreference(Listener updateListener) + { + super(updateListener); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.CALIBRATION; + } + + /** + * Indicates if the calibration dialog should be shown. + */ + public boolean isHideCalibrationDialog() + { + if(mHideCalibrationDialog == null) + { + mHideCalibrationDialog = mPreferences.getBoolean(PREFERENCE_KEY_HIDE_CALIBRATION_DIALOG, false); + } + + return mHideCalibrationDialog; + } + + /** + * Sets the show calibration dialog + */ + public void setHideCalibrationDialog(boolean hide) + { + mPreferences.putBoolean(PREFERENCE_KEY_HIDE_CALIBRATION_DIALOG, hide); + mHideCalibrationDialog = hide; + notifyPreferenceUpdated(); + } + + /** + * Indicates if Vector (SIMD) operations are enabled. + */ + public boolean isVectorEnabled() + { + if(mVectorEnabled == null) + { + mVectorEnabled = mPreferences.getBoolean(PREFERENCE_KEY_VECTOR_ENABLED, true); + } + + return mVectorEnabled; + } + + /** + * Enables or disables Vector (SIMD) operations + */ + public void setVectorEnabled(boolean enabled) + { + mPreferences.putBoolean(PREFERENCE_KEY_VECTOR_ENABLED, enabled); + mVectorEnabled = enabled; + notifyPreferenceUpdated(); + } +} diff --git a/src/main/java/io/github/dsheirer/record/RecorderFactory.java b/src/main/java/io/github/dsheirer/record/RecorderFactory.java index 9ce170d0..42a69a3b 100644 --- a/src/main/java/io/github/dsheirer/record/RecorderFactory.java +++ b/src/main/java/io/github/dsheirer/record/RecorderFactory.java @@ -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 @@ -26,7 +26,8 @@ import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder; import io.github.dsheirer.module.decode.p25.audio.P25P2CallSequenceRecorder; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.binary.BinaryRecorder; -import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.ComplexSamplesWaveRecorder; +import io.github.dsheirer.record.wave.NativeBufferWaveRecorder; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import io.github.dsheirer.util.StringUtils; @@ -158,12 +159,24 @@ public class RecorderFactory /** * Constructs a baseband recorder for use in a processing chain. */ - public static ComplexBufferWaveRecorder getBasebandRecorder(String channelName, UserPreferences userPreferences) + public static ComplexSamplesWaveRecorder getBasebandRecorder(String channelName, UserPreferences userPreferences) { StringBuilder sb = new StringBuilder(); sb.append(getRecordingBasePath(userPreferences)); sb.append(File.separator).append(StringUtils.replaceIllegalCharacters(channelName)).append("_baseband"); - return new ComplexBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString()); + return new ComplexSamplesWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString()); + } + + /** + * Constructs a baseband recorder for use in a processing chain. + */ + public static NativeBufferWaveRecorder getTunerRecorder(String channelName, UserPreferences userPreferences) + { + StringBuilder sb = new StringBuilder(); + sb.append(getRecordingBasePath(userPreferences)); + sb.append(File.separator).append(StringUtils.replaceIllegalCharacters(channelName)).append("_baseband"); + + return new NativeBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString()); } } diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java index e4454b9c..0168ba2f 100644 --- a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java @@ -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,12 +14,10 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.record.binary; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; -import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,21 +25,18 @@ import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.file.Path; -import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Iterator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class BinaryReader implements Iterator, AutoCloseable +public class BinaryReader implements Iterator, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger(BinaryReader.class); - private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("Binary Reader"); private int mBufferSize; private InputStream mInputStream; private Path mPath; - private ReusableByteBuffer mNextBuffer; - private TimestampTracker mTimestampTracker = new TimestampTracker(); + private ByteBuffer mNextBuffer; /** * Constructs a binary reader @@ -55,7 +49,6 @@ public class BinaryReader implements Iterator, AutoCloseable mBufferSize = bufferSize; mPath = path; mInputStream = new BufferedInputStream(new FileInputStream(path.toFile())); - mTimestampTracker.processFileName(path.toString()); getNext(); } @@ -84,9 +77,9 @@ public class BinaryReader implements Iterator, AutoCloseable * @return */ @Override - public ReusableByteBuffer next() + public ByteBuffer next() { - ReusableByteBuffer current = mNextBuffer; + ByteBuffer current = mNextBuffer; getNext(); return current; } @@ -98,118 +91,27 @@ public class BinaryReader implements Iterator, AutoCloseable { try { - mNextBuffer = mBufferQueue.getBuffer(mBufferSize); + byte[] readBytes = new byte[mBufferSize]; + int bytesRead = mInputStream.read(readBytes); - int bytesRead = mInputStream.read(mNextBuffer.getBytes(), 0, mBufferSize); - - mNextBuffer.setTimestamp(mTimestampTracker.getTimestamp()); - mTimestampTracker.updateBytesProcessed(bytesRead); + if(bytesRead < readBytes.length) + { + readBytes = Arrays.copyOf(readBytes, bytesRead); + } if(bytesRead > 0) { - if(bytesRead < mBufferSize) - { - ReusableByteBuffer partialBuffer = mBufferQueue.getBuffer(bytesRead); - System.arraycopy(mNextBuffer.getBytes(), 0, partialBuffer.getBytes(), 0, bytesRead); - partialBuffer.setTimestamp(mNextBuffer.getTimestamp()); - mNextBuffer.decrementUserCount(); - mNextBuffer = partialBuffer; - } + mNextBuffer = ByteBuffer.wrap(readBytes); } else { - mNextBuffer.decrementUserCount(); mNextBuffer = null; } } catch(IOException e) { mLog.error("Error reading binary file [" + mPath.toString() + "]", e); - mNextBuffer.decrementUserCount(); mNextBuffer = null; } } - - public class TimestampTracker - { - private final Pattern TIMESTAMP_BITRATE_PATTERN = Pattern.compile(".*(\\d{8}_\\d{6})_(\\d{4,8})BPS_.*.bits"); - private final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMdd_HHmmss"); - private Double mBitRate = null; - private long mTimestamp; - - public TimestampTracker() - { - } - - /** - * Processes the file name to attempt to extract the file starting timestamp and bit rate. - * @param filename - */ - public void processFileName(String filename) - { - try - { - Matcher m = TIMESTAMP_BITRATE_PATTERN.matcher(filename); - - if(m.find()) - { - long timestamp = SDF.parse(m.group(1)).getTime(); - int bitRate = Integer.parseInt(m.group(2)); - - setTimestamp(timestamp); - setBitRate(bitRate); - return; - } - } - catch(Exception e) - { - mLog.error("Couldn't parse timestamp and bit rate from filename [" + filename + "]"); - } - } - - /** - * Sets the expected bit rate (ie bits per second) for the tracker - */ - public void setBitRate(int bitRate) - { - mBitRate = (double)bitRate; - } - - /** - * Sets the timestamp for the tracker, otherwise current system time is assumed. - */ - public void setTimestamp(long timestamp) - { - mTimestamp = timestamp; - } - - /** - * Gets the currently tracked timestamp when bits per second has been previously specified, otherwise - * returns current system time. - * - * @return current tracked timestamp or current system time. - */ - public long getTimestamp() - { - if(mBitRate != null) - { - return mTimestamp; - } - - return System.currentTimeMillis(); - } - - /** - * Updates the timestamp based on the number of bytes processed and the specified bit rate. - * - * @param bytesProcessed thus far - */ - public void updateBytesProcessed(int bytesProcessed) - { - if(mBitRate != null) - { - mTimestamp += (long)(((double)bytesProcessed * 8.0) / mBitRate * 1000.0); - } - } - } } diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java index b433437f..3a1c8489 100644 --- a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java @@ -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,16 +14,15 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.record.binary; -import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; import io.github.dsheirer.module.Module; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableByteBufferListener; -import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import io.github.dsheirer.sample.buffer.IByteBufferListener; +import io.github.dsheirer.util.Dispatcher; import io.github.dsheirer.util.StringUtils; import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; @@ -37,7 +35,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.EnumSet; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -48,14 +45,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * The contents of the file are the raw bytes as demodulated by the decoder with * no header or timestamps, other than the timestamp included in the filename. */ -public class BinaryRecorder extends Module implements IReusableByteBufferListener +public class BinaryRecorder extends Module implements IByteBufferListener { private final static Logger mLog = LoggerFactory.getLogger(BinaryRecorder.class); private static final int MAX_RECORDING_BYTE_SIZE = 524288; //500 kB - private ContinuousReusableBufferProcessor mBufferProcessor = - new ContinuousReusableBufferProcessor<>(500, 50); - + private Dispatcher mBufferProcessor = new Dispatcher<>(500, + "sdrtrunk binary recorder", ByteBuffer.allocate(0)); private AtomicBoolean mRunning = new AtomicBoolean(); private Path mBaseRecordingPath; private String mRecordingIdentifier; @@ -135,7 +131,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene } @Override - public Listener getReusableByteBufferListener() + public Listener getByteBufferListener() { return mBufferProcessor; } @@ -148,7 +144,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene /** * Binary writer implementation for reusable byte buffers delivered from buffer processor */ - public class BinaryWriter implements Listener> + public class BinaryWriter implements Listener { private Path mCurrentPath; private WritableByteChannel mWritableByteChannel; @@ -160,7 +156,6 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene mCurrentPath = path; mWritableByteChannel = Files.newByteChannel(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE)); -// mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString()); } } @@ -209,42 +204,36 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene /** * Primary receive method for incoming byte buffers - * @param reusableComplexBuffers to record + * @param byteBuffer to record */ @Override - public void receive(List reusableComplexBuffers) + public void receive(ByteBuffer byteBuffer) { - for(ReusableByteBuffer buffer: reusableComplexBuffers) + if(mWritableByteChannel != null) { - if(mWritableByteChannel != null) + try { - try - { - ByteBuffer byteBuffer = ByteBuffer.wrap(buffer.getSamplesCopy()); - mBytesRecordedCounter += mWritableByteChannel.write(byteBuffer); + mBytesRecordedCounter += mWritableByteChannel.write(byteBuffer); - if(mBytesRecordedCounter > MAX_RECORDING_BYTE_SIZE) - { - cycleRecording(); - } - } - catch(IOException ioe) + if(mBytesRecordedCounter > MAX_RECORDING_BYTE_SIZE) { - mLog.error("Error recording demodulated bits to file [" + - (mCurrentPath != null ? mCurrentPath.toString() : "no file") + "] - stopping recorder"); - - try - { - stop(); - } - catch(IOException ioe2) - { - mLog.error("Error stopping recorder after write error", ioe2.getLocalizedMessage()); - } + cycleRecording(); } } + catch(IOException ioe) + { + mLog.error("Error recording demodulated bits to file [" + + (mCurrentPath != null ? mCurrentPath.toString() : "no file") + "] - stopping recorder"); - buffer.decrementUserCount(); + try + { + stop(); + } + catch(IOException ioe2) + { + mLog.error("Error stopping recorder after write error", ioe2.getLocalizedMessage()); + } + } } } } diff --git a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/ComplexSamplesWaveRecorder.java similarity index 61% rename from src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java rename to src/main/java/io/github/dsheirer/record/wave/ComplexSamplesWaveRecorder.java index 1086f48c..39140ded 100644 --- a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java +++ b/src/main/java/io/github/dsheirer/record/wave/ComplexSamplesWaveRecorder.java @@ -1,28 +1,31 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.record.wave; -import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.IComplexSamplesListener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; @@ -32,28 +35,26 @@ import javax.sound.sampled.AudioFormat; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * WAVE audio recorder module for recording complex (I&Q) samples to a wave file */ -public class ComplexBufferWaveRecorder extends Module implements IReusableComplexBufferListener, - Listener, ISourceEventListener +public class ComplexSamplesWaveRecorder extends Module implements IComplexSamplesListener, + Listener, ISourceEventListener { - private final static Logger mLog = LoggerFactory.getLogger(ComplexBufferWaveRecorder.class); + private final static Logger mLog = LoggerFactory.getLogger(ComplexSamplesWaveRecorder.class); - private ContinuousReusableBufferProcessor mBufferProcessor = - new ContinuousReusableBufferProcessor<>(500, 50); + private Dispatcher mBufferProcessor = new Dispatcher<>(500, + "sdrtrunk complex wave recorder", new ComplexSamples(new float[0], new float[0])); private AtomicBoolean mRunning = new AtomicBoolean(); - private ReusableBufferWaveWriter mWriter; + private BufferWaveWriter mWriter; private String mFilePrefix; private Path mFile; private AudioFormat mAudioFormat; - public ComplexBufferWaveRecorder(float sampleRate, String filePrefix) + public ComplexSamplesWaveRecorder(float sampleRate, String filePrefix) { mFilePrefix = filePrefix; setSampleRate(sampleRate); @@ -91,7 +92,7 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple sb.append(".wav"); mFile = Paths.get(sb.toString()); - mWriter = new ReusableBufferWaveWriter(mAudioFormat, mFile); + mWriter = new BufferWaveWriter(mAudioFormat, mFile); mBufferProcessor.setListener(mWriter); mBufferProcessor.start(); @@ -117,7 +118,7 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple { //Thread this operation so that it doesn't tie up the calling thread. The wave writer //close method will also rename the file and this can sometimes take a few seconds. - ThreadPool.SCHEDULED.schedule(new Runnable() + ThreadPool.CACHED.submit(new Runnable() { @Override public void run() @@ -131,20 +132,20 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple mLog.error("Error closing baseband I/Q recorder", ioe); } } - }, 0, TimeUnit.MILLISECONDS); + }); } } } @Override - public void receive(ReusableComplexBuffer buffer) + public void receive(ComplexSamples buffer) { //Queue the buffer with the buffer processor so that recording occurs on the buffer processor thread mBufferProcessor.receive(buffer); } @Override - public Listener getReusableComplexBufferListener() + public Listener getComplexSamplesListener() { return this; } @@ -173,37 +174,32 @@ public class ComplexBufferWaveRecorder extends Module implements IReusableComple } /** - * Wave writer implementation for reusable complex buffers delivered from buffer processor + * Wave writer implementation for complex buffers delivered from buffer processor */ - public class ReusableBufferWaveWriter extends WaveWriter implements Listener> + public class BufferWaveWriter extends WaveWriter implements Listener { - public ReusableBufferWaveWriter(AudioFormat format, Path file) throws IOException + public BufferWaveWriter(AudioFormat format, Path file) throws IOException { super(format, file); } @Override - public void receive(List reusableComplexBuffers) + public void receive(ComplexSamples complexSamples) { boolean error = false; - for(ReusableComplexBuffer reusableComplexBuffer: reusableComplexBuffers) + if(!error) { - if(!error) + try { - try - { - mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(reusableComplexBuffer)); - } - catch(IOException ioe) - { - mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); - error = true; - stop(); - } + mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(complexSamples)); + } + catch(IOException ioe) + { + mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); + error = true; + stop(); } - - reusableComplexBuffer.decrementUserCount(); } } } diff --git a/src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java new file mode 100644 index 00000000..69484f02 --- /dev/null +++ b/src/main/java/io/github/dsheirer/record/wave/NativeBufferWaveRecorder.java @@ -0,0 +1,208 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.record.wave; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; +import io.github.dsheirer.module.Module; +import io.github.dsheirer.sample.ConversionUtils; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; +import io.github.dsheirer.source.ISourceEventListener; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; +import io.github.dsheirer.util.ThreadPool; +import io.github.dsheirer.util.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sound.sampled.AudioFormat; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * WAVE audio recorder module for recording complex (I&Q) samples to a wave file + */ +public class NativeBufferWaveRecorder extends Module implements Listener, ISourceEventListener +{ + private final static Logger mLog = LoggerFactory.getLogger(ComplexSamplesWaveRecorder.class); + + private Dispatcher mBufferProcessor = new Dispatcher<>(500, + "sdrtrunk-native buffer wave recorder", new NativeBufferPoisonPill()); + + private AtomicBoolean mRunning = new AtomicBoolean(); + private INativeBufferWaveWriter mWriter; + private String mFilePrefix; + private Path mFile; + private AudioFormat mAudioFormat; + + public NativeBufferWaveRecorder(float sampleRate, String filePrefix) + { + mFilePrefix = filePrefix; + setSampleRate(sampleRate); + } + + public void setSampleRate(float sampleRate) + { + if(mAudioFormat == null || mAudioFormat.getSampleRate() != sampleRate) + { + mAudioFormat = new AudioFormat(sampleRate, 16, 2, true, false); + + if(mRunning.get()) + { + stop(); + start(); + } + } + } + + public Path getFile() + { + return mFile; + } + + public void start() + { + if(mRunning.compareAndSet(false, true)) + { + try + { + StringBuilder sb = new StringBuilder(); + sb.append(mFilePrefix); + sb.append("_"); + sb.append(TimeStamp.getTimeStamp("_")); + sb.append(".wav"); + mFile = Paths.get(sb.toString()); + + mWriter = new INativeBufferWaveWriter(mAudioFormat, mFile); + + mBufferProcessor.setListener(mWriter); + mBufferProcessor.start(); + } + catch(IOException io) + { + mLog.error("Error starting complex baseband recorder", io); + } + } + } + + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + if(mBufferProcessor != null) + { + mBufferProcessor.stop(); + mBufferProcessor.setListener(null); + } + + if(mWriter != null) + { + //Thread this operation so that it doesn't tie up the calling thread. The wave writer + //close method will also rename the file and this can sometimes take a few seconds. + ThreadPool.CACHED.submit(new Runnable() + { + @Override + public void run() + { + try + { + mWriter.close(); + } + catch(IOException ioe) + { + mLog.error("Error closing baseband I/Q recorder", ioe); + } + } + }); + } + } + } + + @Override + public void receive(INativeBuffer nativeBuffer) + { + //Queue the buffer with the buffer processor so that recording occurs on the buffer processor thread + mBufferProcessor.receive(nativeBuffer); + } + + public Listener getReusableComplexBufferListener() + { + return this; + } + + @Override + public void reset() + { + } + + @Override + public Listener getSourceEventListener() + { + return new Listener() + { + @Override + public void receive(SourceEvent sourceEvent) + { + switch(sourceEvent.getEvent()) + { + case NOTIFICATION_SAMPLE_RATE_CHANGE: + setSampleRate(sourceEvent.getValue().floatValue()); + break; + } + } + }; + } + + /** + * Wave writer implementation for complex samples delivered from buffer processor + */ + public class INativeBufferWaveWriter extends WaveWriter implements Listener + { + public INativeBufferWaveWriter(AudioFormat format, Path file) throws IOException + { + super(format, file); + } + + @Override + public void receive(INativeBuffer nativeBuffer) + { + boolean error = false; + + Iterator iterator = nativeBuffer.iteratorInterleaved(); + + while(iterator.hasNext() & !error) + { + try + { + mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(iterator.next())); + } + catch(IOException ioe) + { + mLog.error("IOException while writing I/Q buffers to wave recorder - stopping recorder", ioe); + error = true; + stop(); + } + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/sample/ConversionUtils.java b/src/main/java/io/github/dsheirer/sample/ConversionUtils.java index 5a48ed61..cc2b91b5 100644 --- a/src/main/java/io/github/dsheirer/sample/ConversionUtils.java +++ b/src/main/java/io/github/dsheirer/sample/ConversionUtils.java @@ -1,6 +1,26 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + package io.github.dsheirer.sample; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -52,12 +72,32 @@ public class ConversionUtils return converted; } + /** + * Converts the float samples into a little-endian 16-bit sample byte buffer. + * + * @param samples - float array of sample data + * @return - little-endian 16-bit sample byte buffer + */ + public static ByteBuffer convertToSigned16BitSamples(ComplexSamples samples) + { + ByteBuffer converted = ByteBuffer.allocate(samples.i().length * 4); + converted.order(ByteOrder.LITTLE_ENDIAN); + + for(int x = 0; x < samples.i().length; x++) + { + converted.putShort((short)(samples.i()[x] * Short.MAX_VALUE)); + converted.putShort((short)(samples.q()[x] * Short.MAX_VALUE)); + } + + return converted; + } + /** * Converts the float samples in a complex buffer to a little endian 16-bit * buffer */ - public static ByteBuffer convertToSigned16BitSamples(ReusableFloatBuffer buffer) + public static ByteBuffer convertToSigned16BitSamples(InterleavedComplexSamples complexSamples) { - return convertToSigned16BitSamples(buffer.getSamples()); + return convertToSigned16BitSamples(complexSamples.samples()); } } diff --git a/src/main/java/io/github/dsheirer/sample/Provider.java b/src/main/java/io/github/dsheirer/sample/Provider.java index a81136cc..583d7a5c 100644 --- a/src/main/java/io/github/dsheirer/sample/Provider.java +++ b/src/main/java/io/github/dsheirer/sample/Provider.java @@ -1,17 +1,17 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014 Dennis Sheirer - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ @@ -19,7 +19,5 @@ package io.github.dsheirer.sample; public interface Provider { - public void setListener( Listener listener ); - - public void removeListener( Listener listener ); + void setListener(Listener listener); } diff --git a/src/main/java/io/github/dsheirer/sample/SampleUtils.java b/src/main/java/io/github/dsheirer/sample/SampleUtils.java new file mode 100644 index 00000000..255a26bc --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/SampleUtils.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.sample; + +import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; +import org.apache.commons.math3.util.FastMath; + +import java.util.Random; + +public class SampleUtils +{ + public static final double TWO_PI = Math.PI * 2.0; + + /** + * Converts from an interleaved complex sample array to a sample record with the + * I and Q in separate arrays. + * @param samples that are interleaved complex samples + * @return deinterleaved complex samples instance + */ + public static ComplexSamples deinterleave(float[] samples) + { + float[] i = new float[samples.length / 2]; + float[] q = new float[samples.length / 2]; + + int offset; + + for(int x = 0; x < i.length; x++) + { + offset = 2 * x; + i[x] = samples[offset]; + q[x] = samples[offset + 1]; + } + + return new ComplexSamples(i, q); + } + + /** + * Converts a complex samples buffer to an array of interleaved (I, Q, etc.) samples + * @param complexSamples to interleave + * @return interleaved samples array + */ + public static float[] interleave(ComplexSamples complexSamples) + { + float[] i = complexSamples.i(); + float[] q = complexSamples.q();; + + float[] complex = new float[i.length + q.length]; + + for(int x = 0; x < i.length; x++) + { + complex[2 * x] = i[x]; + complex[2 * x + 1] = q[x]; + } + + return complex; + } + + /** + * Generates a buffer of complex samples where each sample's vector length is unity (1.0) + * @param count of buffer samples + * @return complex buffer + */ + public static ComplexSamples generateComplex(int count) + { + Random random = new Random(); + + float[] i = new float[count]; + float[] q = new float[count]; + + for(int x = 0; x < i.length; x++) + { + //Random angle in radians in range of -PI to + PI + double angle = random.nextDouble() * TWO_PI - Math.PI; + + i[x] = (float)Math.cos(angle); + q[x] = (float)Math.sin(angle); + } + + return new ComplexSamples(i, q); + } + + /** + * Generates random complex samples with vector length in range 0.5 to 1.0 (unity). + * @param count of samples + * @return complex sample buffer + */ + public static ComplexSamples generateComplexRandomVectorLength(int count) + { + Random random = new Random(); + ComplexSamples unitySamples = generateComplex(count); + float[] i = unitySamples.i(); + float[] q = unitySamples.q(); + + float gain; + + //Apply random gain reduction to samples in range 0.5 to 1.0 gain + for(int x = 0; x < i.length; x++) + { + gain = 0.5f + random.nextFloat() / 2.0f; + i[x] *= gain; + q[x] *= gain; + } + + return new ComplexSamples(i, q); + } + + public static Complex multiply(Complex a, Complex b) + { + return new Complex((a.inphase() * b.inphase()) - (a.quadrature() * b.quadrature()), + ((a.quadrature() * b.inphase()) + (a.inphase() * b.quadrature()))); + } + + public static Complex minus(Complex a, Complex b) + { + return new Complex(a.inphase() - b.inphase(), a.quadrature() - b.quadrature()); + } + + public static double magnitude(Complex sample) + { + return FastMath.sqrt(magnitudeSquared(sample)); + } + + public static int magnitudeSquared(Complex sample) + { + return (int)((sample.inphase() * sample.inphase()) + (sample.quadrature() * sample.quadrature())); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java deleted file mode 100644 index 9bc4bfe6..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java +++ /dev/null @@ -1,23 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.adapter; - -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -public abstract class AbstractSampleAdapter -{ - public abstract T convert(byte[] sampleBytes); -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java deleted file mode 100644 index 21846439..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java +++ /dev/null @@ -1,39 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.adapter; - -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; - -public abstract class ComplexSampleAdapter extends AbstractSampleAdapter -{ - private ReusableComplexBufferQueue mReusableBufferQueue; - - /** - * Constructs a real sample adapter - * - * @param debugName to use for debug logging - */ - public ComplexSampleAdapter(String debugName) - { - mReusableBufferQueue = new ReusableComplexBufferQueue(debugName); - } - - protected ReusableComplexBuffer getBuffer(int size) - { - return mReusableBufferQueue.getBuffer(size); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java index 2b39e9c2..0efef045 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java @@ -1,21 +1,25 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +29,7 @@ import java.nio.ByteOrder; /** * Converts 16-bit/2-byte little endian byte data into a reusable buffer of complex float sample data */ -public class ComplexShortAdapter extends ComplexSampleAdapter +public class ComplexShortAdapter implements ISampleAdapter { private final static Logger mLog = LoggerFactory.getLogger(ComplexShortAdapter.class); private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; @@ -33,19 +37,15 @@ public class ComplexShortAdapter extends ComplexSampleAdapter /** * Constructs a real sample adapter - * - * @param debugName to use for debug logging */ - public ComplexShortAdapter(String debugName) + public ComplexShortAdapter() { - super(debugName); } @Override - public ReusableComplexBuffer convert(byte[] samples) + public INativeBuffer convert(byte[] samples) { - ReusableComplexBuffer reusableBuffer = getBuffer(samples.length / 2); - float[] convertedSamples = reusableBuffer.getSamples(); + float[] convertedSamples = new float[samples.length / 2]; int pointer = 0; @@ -60,7 +60,9 @@ public class ComplexShortAdapter extends ComplexSampleAdapter pointer++; } - return reusableBuffer; + long now = System.currentTimeMillis(); + + return new FloatNativeBuffer(convertedSamples, now); } /** diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java new file mode 100644 index 00000000..aa1692f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java @@ -0,0 +1,24 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.adapter; + +public interface ISampleAdapter +{ + T convert(byte[] sampleBytes); +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java index fd1f5180..74ddb4b1 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java @@ -1,21 +1,23 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import io.github.dsheirer.source.mixer.MixerChannel; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; @@ -34,10 +36,8 @@ public class RealChannelShortAdapter extends RealSampleAdapter private MixerChannel mMixerChannel; private ByteBuffer mByteBuffer; - public RealChannelShortAdapter(MixerChannel channel, String debugName) + public RealChannelShortAdapter(MixerChannel channel) { - super(debugName); - /* Only use with LEFT/RIGHT channels */ Validate.isTrue(channel != MixerChannel.MONO); @@ -45,10 +45,9 @@ public class RealChannelShortAdapter extends RealSampleAdapter } @Override - public ReusableFloatBuffer convert(byte[] samples) + public float[] convert(byte[] samples) { - ReusableFloatBuffer reusableFloatBuffer = getBuffer(samples.length / 4); - float[] convertedSamples = reusableFloatBuffer.getSamples(); + float[] convertedSamples = new float[samples.length / 4]; mByteBuffer = ByteBuffer.wrap(samples); @@ -75,7 +74,7 @@ public class RealChannelShortAdapter extends RealSampleAdapter } } - return reusableFloatBuffer; + return convertedSamples; } /** diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java index 70d05bcd..f017e0e3 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java @@ -1,39 +1,29 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - -public abstract class RealSampleAdapter extends AbstractSampleAdapter +public abstract class RealSampleAdapter implements ISampleAdapter { - private ReusableBufferQueue mReusableBufferQueue; - /** * Constructs a real sample adapter - * - * @param debugName to use for debug logging */ - public RealSampleAdapter(String debugName) + public RealSampleAdapter() { - mReusableBufferQueue = new ReusableBufferQueue(debugName); - } - - protected ReusableFloatBuffer getBuffer(int size) - { - return mReusableBufferQueue.getBuffer(size); } } diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java index 683c04d1..2c4d8b53 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java @@ -1,22 +1,23 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; - import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -30,19 +31,15 @@ public class RealShortAdapter extends RealSampleAdapter /** * Constructs a real sample adapter - * - * @param debugName to use for debug logging */ - public RealShortAdapter(String debugName) + public RealShortAdapter() { - super(debugName); } @Override - public ReusableFloatBuffer convert(byte[] samples) + public float[] convert(byte[] samples) { - ReusableFloatBuffer reusableFloatBuffer = getBuffer(samples.length / 2); - float[] convertedSamples = reusableFloatBuffer.getSamples(); + float[] convertedSamples = new float[samples.length / 2]; int pointer = 0; @@ -57,7 +54,7 @@ public class RealShortAdapter extends RealSampleAdapter pointer++; } - return reusableFloatBuffer; + return convertedSamples; } /** diff --git a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java deleted file mode 100644 index 0a8a5ce5..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java +++ /dev/null @@ -1,177 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class AbstractReusableBuffer -{ - private final static Logger mLog = LoggerFactory.getLogger(AbstractReusableBuffer.class); - - protected IReusableBufferDisposedListener mBufferDisposedListener; - protected long mTimestamp; - protected AtomicInteger mUserCount = new AtomicInteger(); - private String mDebugName; - - public AbstractReusableBuffer(IReusableBufferDisposedListener bufferDisposedListener, long timestamp) - { - mBufferDisposedListener = bufferDisposedListener; - mTimestamp = timestamp; - } - - public AbstractReusableBuffer(IReusableBufferDisposedListener bufferDisposedListener) - { - mBufferDisposedListener = bufferDisposedListener; - } - - public void dispose() - { - mBufferDisposedListener = null; - } - - public void setDebugName(String debugName) - { - mDebugName = debugName; - } - - public String name() - { - return (mDebugName != null ? mDebugName : "(null)") + " User Count:" + getUserCount(); - } - - public String toString() - { - return name(); - } - - /** - * Reference timestamp for this complex buffer. Timestamp is relative to the first sample in the buffer. - * @return timestamp as milliseconds since epoch - */ - public long getTimestamp() - { - return mTimestamp; - } - - public void setTimestamp(long timestamp) - { - mTimestamp = timestamp; - } - - /** - * Sets the user count to 0 and recycles this buffer - */ - public void clearUserCount() - { - mUserCount.set(0); - recycle(); - } - - /** - * Decrements the user count for this buffer to indicate that the user/receiver of this buffer is finished using - * the buffer. - * - * User count is established prior to sending this buffer to each listener and each listener invokes this method to - * indicate that they have finished processing the buffer so that when the user count reaches zero, this buffer - * can be reused. - * - * This method is thread-safe. - */ - public synchronized void decrementUserCount() - { - if(mUserCount.decrementAndGet() <= 0) - { - recycle(); - } - } - - /** - * Sends this buffer back to the owning buffer queue for reuse - */ - private synchronized void recycle() - { - prepareForRecycle(); - - if(mBufferDisposedListener != null) - { - mBufferDisposedListener.disposed(this); - } - - if(mUserCount.get() < 0) - { - mUserCount.set(0); - throw new IllegalStateException("User count is below zero. This indicates that this buffer's decrement" + - " user count was invoked by more than the expected user count"); - } - } - - /** - * Invoked just prior to notifying the owner that this buffer is ready for prepareForRecycle. This method - * is intended for sub-class implementations to perform any prepareForRecycle cleanup actions. - */ - protected void prepareForRecycle() - { - //No-op in this abstract class - } - - /** - * Increments the user count to indicate that this buffer will be sent to another user. - * - * Users/receivers of this buffer that wish to forward the buffer to additional users should increment the expected - * user count prior to invoking the decrementUserCount() to indicate that the original receiver of the buffer is - * finished to avoid the user count reaching zero and the buffer being flagged as ready for reuse. - * - * This method is thread-safe. - */ - public void incrementUserCount() - { - mUserCount.incrementAndGet(); - } - - /** - * Increments the user count by the specified count to indicate that this buffer will be sent to additional users. - * - * Users/receivers of this buffer that wish to forward the buffer to additional users should increment the expected - * user count prior to invoking the decrementUserCount() to indicate that the original receiver of the buffer is - * finished to avoid the user count reaching zero and the buffer being flagged as ready for reuse. - * - * This method is thread-safe. - * - * @param additionalUserCount to add to the current user count - */ - public void incrementUserCount(int additionalUserCount) - { - if(additionalUserCount >= 0) - { - mUserCount.addAndGet(additionalUserCount); - } - else - { - mLog.debug("Request to add negative user count [" + additionalUserCount + "] ignored"); - } - } - - /** - * Number of users currently registered for this buffer - */ - public int getUserCount() - { - return mUserCount.get(); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java deleted file mode 100644 index dd07dbab..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBufferQueue.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * ***************************************************************************** - * 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 - * **************************************************************************** - */ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Queue; -import java.util.concurrent.LinkedTransferQueue; - -public abstract class AbstractReusableBufferQueue - implements IReusableBufferDisposedListener -{ - private final static Logger mLog = LoggerFactory.getLogger(AbstractReusableBufferQueue.class); - - private Queue mReusableBufferQueue = new LinkedTransferQueue<>(); - private int mBufferCount = 0; - private String mDebugName; - - //Set to true to log when reusable buffers are created to monitor proper user count management - protected boolean mBufferCreationLoggingEnabled = false; - - /** - * Base queue for managing reusable buffers. - * - * @param debugName to associate a debug string with this buffer instance - */ - public AbstractReusableBufferQueue(String debugName) - { - mDebugName = debugName; - } - - /** - * Base queue for managing reusable buffers. - */ - public AbstractReusableBufferQueue() - { - } - - /** - * Turns on/off logging of buffer creation - */ - public void setDebugLogging(boolean enabled) - { - mBufferCreationLoggingEnabled = enabled; - } - - /** - * Disposes of any reclaimed buffers to prepare this queue for disposal. - */ - public void dispose() - { - T buffer = mReusableBufferQueue.poll(); - - while(buffer != null) - { - buffer.dispose(); - buffer = mReusableBufferQueue.poll(); - } - - mBufferCount = 0; - } - - /** - * Implements buffer disposed listener interface. Disposed (ie user count = 0) buffers will automatically callback - * to this method to indicate when they are disposed. - * - * @param reusableBuffer that has been disposed - */ - @Override - public void disposed(T reusableBuffer) - { - mReusableBufferQueue.offer(reusableBuffer); - } - - /** - * Get a recycled buffer from the queue - */ - protected T getRecycledBuffer() - { - return mReusableBufferQueue.poll(); - } - - /** - * Increments the count of buffers managed by this queue. Note: this method is NOT thread safe - */ - protected void incrementBufferCount() - { - mBufferCount++; - - if(mBufferCreationLoggingEnabled) - { - mLog.debug("Buffer Created - count:" + mBufferCount + - " debug:" + (mDebugName != null ? mDebugName : "null") + " class:" + this.getClass()); - } - } - - /** - * Current count of buffers created by this queue - */ - protected int getBufferCount() - { - return mBufferCount; - } - - /** - * Debug name for this queue instance - */ - protected String getDebugName() - { - return mDebugName; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java new file mode 100644 index 00000000..3945b42a --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferListener.java @@ -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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.buffer; + +import io.github.dsheirer.sample.Listener; + +import java.nio.ByteBuffer; + +public interface IByteBufferListener +{ + Listener getByteBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java new file mode 100644 index 00000000..7e28d609 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/buffer/IByteBufferProvider.java @@ -0,0 +1,41 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.buffer; + +import io.github.dsheirer.sample.Listener; + +import java.nio.ByteBuffer; + +public interface IByteBufferProvider +{ + /** + * Adds the listener to receive complex buffer samples + */ + void setBufferListener(Listener listener); + + /** + * Removes the listener from receiving complex buffer samples + */ + void removeBufferListener(Listener listener); + + /** + * Indicates if there are any complex buffer listeners registered + */ + boolean hasBufferListeners(); +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java deleted file mode 100644 index 67cbf293..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferDisposedListener.java +++ /dev/null @@ -1,25 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -public interface IReusableBufferDisposedListener -{ - /** - * Notifies the listener that the timestamped buffer has been disposed and can now be reused. - * @param t reusableBuffer that has been disposed - */ - void disposed(T t); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java deleted file mode 100644 index 41795f92..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferProvider.java +++ /dev/null @@ -1,31 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableBufferProvider -{ - /** - * Adds the listener to receive complex buffer samples - */ - void setBufferListener(Listener listener); - - /** - * Removes the listener from receiving complex buffer samples - */ - void removeBufferListener(); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java deleted file mode 100644 index 2aba9f9a..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferListener.java +++ /dev/null @@ -1,23 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableByteBufferListener -{ - Listener getReusableByteBufferListener(); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java deleted file mode 100644 index 80de66d5..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferListener.java +++ /dev/null @@ -1,23 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableComplexBufferListener -{ - Listener getReusableComplexBufferListener(); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java b/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java deleted file mode 100644 index 14316bb3..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableComplexBufferProvider.java +++ /dev/null @@ -1,36 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.Listener; - -public interface IReusableComplexBufferProvider -{ - /** - * Adds the listener to receive complex buffer samples - */ - void addBufferListener(Listener listener); - - /** - * Removes the listener from receiving complex buffer samples - */ - void removeBufferListener(Listener listener); - - /** - * Indicates if there are any complex buffer listeners registered - */ - boolean hasBufferListeners(); -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ITimestamped.java b/src/main/java/io/github/dsheirer/sample/buffer/ITimestamped.java new file mode 100644 index 00000000..cff7b6fa --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/buffer/ITimestamped.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.sample.buffer; + +/** + * Interface for objects that have timestamps. + */ +public interface ITimestamped +{ + long timestamp(); +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java deleted file mode 100644 index 874f1a9b..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/OverflowableReusableBufferTransferQueue.java +++ /dev/null @@ -1,69 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.OverflowableTransferQueue; - -public class OverflowableReusableBufferTransferQueue extends OverflowableTransferQueue -{ - /** - * Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for - * monitoring queue size. When the queue size exceeds maximum size (overflow), all inbound elements are ignored - * until the queue size is reduced to or below the reset threshold. - * - * This implementation includes special handling for reusable complex buffers. - * - * @param maximumSize of the queue. Overflow state will occur once queue size exceeds this value. - * @param resetThreshold for resetting overflow state to normal, once queue size is at or below this value. - */ - public OverflowableReusableBufferTransferQueue(int maximumSize, int resetThreshold) - { - super(maximumSize, resetThreshold); - } - - /** - * Overrides the overflow method to decrement the user count on any buffers that are being discarded when the queue - * is in an overflow state. - * - * @param t reusableBuffer that will be discarded - */ - @Override - protected void overflow(T t) - { - t.decrementUserCount(); - } - - /** - * Overrides the buffer clear method to decrement the user count on each buffer that is being cleared from the queue. - */ - @Override - public void clear() - { - synchronized(mQueue) - { - T buffer = mQueue.poll(); - - while(buffer != null) - { - buffer.decrementUserCount(); - buffer = mQueue.poll(); - } - - mCounter.set(0); - mOverflow.set(false); - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java deleted file mode 100644 index 59fda806..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferAssembler.java +++ /dev/null @@ -1,80 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.real.RealSampleListener; - -/** - * Assembles single float samples into a ReusableFloatBuffer containing an array of floats - */ -public class ReusableBufferAssembler implements RealSampleListener -{ - private int mBufferSize; - private int mBufferPointer; - private ReusableFloatBuffer mCurrentBuffer; - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Real"); - - private Listener mListener; - - public ReusableBufferAssembler(int bufferSize) - { - mBufferSize = bufferSize; - } - - public void dispose() - { - mListener = null; - } - - public void reset() - { - mCurrentBuffer.decrementUserCount(); - mCurrentBuffer = null; - mBufferPointer = 0; - } - - @Override - public void receive(float sample) - { - if(mCurrentBuffer == null) - { - mCurrentBuffer = mReusableBufferQueue.getBuffer(mBufferSize); - mBufferPointer = 0; - } - - mCurrentBuffer.getSamples()[mBufferPointer++] = sample; - - if (mBufferPointer >= mBufferSize) - { - if (mListener != null) - { - mListener.receive(mCurrentBuffer); - } - else - { - mCurrentBuffer.decrementUserCount(); - } - - mCurrentBuffer = null; - } - } - - public void setListener(Listener listener) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java deleted file mode 100644 index fcd36e73..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferBroadcaster.java +++ /dev/null @@ -1,48 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.sample.Broadcaster; -import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableBufferBroadcaster extends Broadcaster -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableBufferBroadcaster.class); - - /** - * Increments the user count for the reusable complex buffer and then broadcasts the buffer to all registered - * listeners. - * - * The total user count is established and applied to the buffer prior to dispatching. If we were to simply - * increment the user count prior to sending to each consumer, there is a possibility that the consumer could - * immediately decrement the user count and prematurely signal that the buffer is ready for disposal before we - * send the buffer to all consumers. - */ - @Override - public void broadcast(T reusableBuffer) - { - for(Listener listener : mListeners) - { - reusableBuffer.incrementUserCount(); - listener.receive(reusableBuffer); - } - - //Decrement user counter for this broadcaster - reusableBuffer.decrementUserCount(); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java deleted file mode 100644 index 7c4373f1..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableBufferQueue.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * ***************************************************************************** - * 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 - * **************************************************************************** - */ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableBufferQueue.class); - - public ReusableBufferQueue(String debugName) - { - super(debugName); - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. The user count is set to one and must be managed throughout the lifecycle - * of the buffer. - * - * @param size of the samples that will be loaded into the buffer - * @return a reusable buffer - */ - public ReusableFloatBuffer getBuffer(int size) - { - ReusableFloatBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableFloatBuffer(this, new float[size]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.resize(size); - buffer.incrementUserCount(); - - return buffer; - } - - /** - * Creates or reuses a buffer and loads it with the samples and timestamp and increments user count to one. - * @param samples to load - * @param timestamp to set - * @return loaded buffer with user count set to one - */ - public ReusableFloatBuffer getBuffer(float[] samples, long timestamp) - { - ReusableFloatBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableFloatBuffer(this, new float[samples.length]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.reloadFrom(samples, timestamp); - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java deleted file mode 100644 index 8ed0169b..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBuffer.java +++ /dev/null @@ -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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import org.apache.commons.lang3.Validate; - -public class ReusableByteBuffer extends AbstractReusableBuffer -{ - private byte[] mSamples; - - /** - * Creates a reusable, timestamped byte buffer using the specified time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers/users are finished using the buffer - * @param bytes of data - * @param timestamp in millis for the buffer - */ - ReusableByteBuffer(IReusableBufferDisposedListener bufferDisposedListener, byte[] bytes, long timestamp) - { - super(bufferDisposedListener, timestamp); - mSamples = bytes; - - Validate.notNull(bufferDisposedListener, "Reusable Byte Buffer Listener cannot be null"); - } - - /** - * Constructs a timestamped byte buffer using the current system time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers are finished using the buffer - * @param bytes of data - */ - public ReusableByteBuffer(IReusableBufferDisposedListener bufferDisposedListener, byte[] bytes) - { - this(bufferDisposedListener, bytes, System.currentTimeMillis()); - } - - /** - * Samples for this buffer - */ - public byte[] getBytes() - { - return mSamples; - } - - /** - * Creates a copy of the samples from this buffer - */ - public byte[] getSamplesCopy() - { - byte[] bytes = getBytes(); - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, bytes.length); - return copy; - } - - /** - * Number of bytes contained in this buffer - */ - public int getSampleCount() - { - return getBytes().length; - } - - /** - * Resizes the internal array to the size argument - * - * @param size for the internal array - */ - protected void resize(int size) - { - if(mSamples.length != size) - { - mSamples = new byte[size]; - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java deleted file mode 100644 index b322097e..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableByteBufferQueue.java +++ /dev/null @@ -1,54 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableByteBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableByteBufferQueue.class); - - public ReusableByteBufferQueue(String debugName) - { - super(debugName); - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. The user count is set to one and must be managed throughout the lifecycle - * of the buffer. - * - * @param size of the samples that will be loaded into the buffer - * @return a reusable buffer - */ - public ReusableByteBuffer getBuffer(int size) - { - ReusableByteBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableByteBuffer(this, new byte[size]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.resize(size); - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java deleted file mode 100644 index 8e401d05..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBuffer.java +++ /dev/null @@ -1,164 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.LinkedList; -import java.util.List; - -public class ReusableChannelResultsBuffer extends AbstractReusableBuffer -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableChannelResultsBuffer.class); - private ReusableComplexBufferQueue mReusableComplexBufferQueue = - new ReusableComplexBufferQueue("ReusableChannelResultsBuffer"); - private LinkedList mEmptyBuffers = new LinkedList<>(); - private LinkedList mFilledBuffers = new LinkedList<>(); - private Integer mLength; - - /** - * Reusable buffer for storing polyphase channelizer results arrays. This buffer is NOT thread safe and is - * designed to be used by only one consumer. The consumer must invoke the prepareForRecycle() method to indicate that this - * buffer can be reused. - * - * @param disposedListener to receive notification when this buffer is ready for prepareForRecycle - */ - ReusableChannelResultsBuffer(IReusableBufferDisposedListener disposedListener) - { - super(disposedListener); - } - - /** - * Recycles this buffer for reuse. - */ - protected void prepareForRecycle() - { - mEmptyBuffers.addAll(mFilledBuffers); - mFilledBuffers.clear(); - mLength = null; - } - - /** - * Gets an empty channel results array correctly sized to the requested length. This method should be used to - * obtain all empty channel results buffers to fill with information and add back to this buffer so that the results - * arrays can be reused. - * - * @param length of the requested array - * @return a float array of the requested length that may contain stale data from previous use. - */ - public float[] getEmptyBuffer(int length) - { - float[] emptyBuffer = mEmptyBuffers.poll(); - - if(emptyBuffer == null || emptyBuffer.length != length) - { - emptyBuffer = new float[length]; - } - - return emptyBuffer; - } - - /** - * Adds the channel results to this buffer. Note: you should only use channel results arrays obtained via the - * getEmptyBuffer() method and filled with data and then added back to this buffer via this method, so that array - * reuse can be accurately controlled. - * - * @param channelResults to add back to this buffer - */ - public void addChannelResults(float[] channelResults) - { - if(mLength == null) - { - mLength = channelResults.length; - } - else if(mLength != channelResults.length) - { - throw new IllegalArgumentException("Channel results length must be the same for all added results"); - } - - mFilledBuffers.offer(channelResults); - } - - /** - * Channel results array contained in this buffer - */ - public List getChannelResults() - { - return mFilledBuffers; - } - - /** - * Extracts a single I/Q interleaved sample buffer from this buffer of channel results arrays - * - * @param iChannelIndex to extract for the inphase sample. The q index is assumed to be one greater than i. - * @return float[] containing interleaved I and Q samples - * @throws IllegalArgumentException if this buffer doesn't contain any samples or if the requested channel is - * not within the channel range of the contained channel results. - */ - public ReusableComplexBuffer getChannel(int iChannelIndex) - { - if(mFilledBuffers.isEmpty()) - { - throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]"); - } - - if(!isValidChannelIndex(iChannelIndex)) - { - throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " + - "results -- max channel is " + mLength); - } - - ReusableComplexBuffer channelBuffer = mReusableComplexBufferQueue.getBuffer(mFilledBuffers.size() * 2); - - float[] samples = channelBuffer.getSamples(); - - int pointer = 0; - - int qChannelIndex = iChannelIndex + 1; - - for(float[] channelResults: mFilledBuffers) - { - try - { - samples[pointer] = channelResults[iChannelIndex]; - pointer++; - samples[pointer] = channelResults[qChannelIndex]; - pointer++; - } - catch(Exception e) - { - mLog.error("Error accessing channel results - iIndex:" + iChannelIndex + - " Results Size: " + mFilledBuffers.size() + - " Samples Length:" + samples.length + - " Pointer:" + pointer, e); - } - } - - return channelBuffer; - } - - /** - * Indicates if the channel argument is valid for the contained channel results. - * - * @param channelIndex number (0 to N) to check - * @return false if the buffer is empty or if the channel is greater than the contained channel results - */ - private boolean isValidChannelIndex(int channelIndex) - { - return (mLength != null) && (channelIndex <= mLength); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java deleted file mode 100644 index 76c027ed..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableChannelResultsBufferQueue.java +++ /dev/null @@ -1,55 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableChannelResultsBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableChannelResultsBufferQueue.class); - - public ReusableChannelResultsBufferQueue(String debugName) - { - super(debugName); - } - - public ReusableChannelResultsBufferQueue() - { - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. - * - * @return a reusable buffer - */ - public ReusableChannelResultsBuffer getBuffer() - { - ReusableChannelResultsBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableChannelResultsBuffer(this); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java deleted file mode 100644 index 49df3b1e..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBuffer.java +++ /dev/null @@ -1,72 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -public class ReusableComplexBuffer extends ReusableFloatBuffer -{ - /** - * Creates a reusable, timestamped complex buffer using the specified time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param disposalListener to be notified when all consumers/users are finished using the buffer - * @param samples of data - * @param timestamp in millis for the buffer - */ - ReusableComplexBuffer(IReusableBufferDisposedListener disposalListener, float[] samples, long timestamp) - { - super(disposalListener, samples, timestamp); - } - - /** - * Constructs a timestamped complex buffer using the current system time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param disposalListener to be notified when all consumers are finished using the buffer - * @param samples of data - */ - protected ReusableComplexBuffer(IReusableBufferDisposedListener disposalListener, float[] samples) - { - this(disposalListener, samples, System.currentTimeMillis()); - } - - /** - * Number of complex samples contained in this buffer - */ - @Override - public int getSampleCount() - { - return getSamples().length / 2; - } - - /** - * Applies the gain value to the samples contained in this buffer - */ - public void applyGain(double gain) - { - float[] samples = getSamples(); - - for(int x = 0; x < samples.length; x++) - { - samples[x] *= gain; - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java deleted file mode 100644 index 789358d8..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java +++ /dev/null @@ -1,174 +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 - * ***************************************************************************** - */ -package io.github.dsheirer.sample.buffer; - -import io.github.dsheirer.dsp.filter.channelizer.SampleTimestampManager; -import io.github.dsheirer.sample.Listener; -import org.apache.commons.lang3.Validate; - -import java.nio.FloatBuffer; - -/** - * Assembles complex float samples into a ComplexBuffer containing an array of floats. Monitors incoming sample count - * to accurately assign a relative timestamp to each assembled buffer. - */ -public class ReusableComplexBufferAssembler -{ -// private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexBufferAssembler.class); - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ReusableComplexBufferAssembler"); - private SampleTimestampManager mTimestampManager; - private FloatBuffer mBuffer; - private int mBufferSize; - private long mCurrentBufferTimestamp; - private Listener mListener; - - public ReusableComplexBufferAssembler(int bufferSize, double sampleRate) - { - Validate.isTrue(bufferSize % 2 == 0); - mBufferSize = bufferSize; - mBuffer = FloatBuffer.allocate(mBufferSize); - mTimestampManager = new SampleTimestampManager(sampleRate); - } - - /** - * Updates the reference timestamp to use for all subsequent samples that are assembled. This timestamp should - * identify the relative timestamp of the first/next sample that is received by this assembler. - * @param timestamp to use relative to the incoming sample arrays. - */ - public void updateTimestamp(long timestamp) - { - mTimestampManager.setReferenceTimestamp(timestamp); - - if(mBuffer.position() == 0) - { - mCurrentBufferTimestamp = timestamp; - } - } - - public void dispose() - { - mReusableComplexBufferQueue.dispose(); - } - - /** - * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered - * listener. - * - * @param inphase sample to assemble - * @param quadrature sample to assemble - */ - public void receive(float inphase, float quadrature) - { - if(mBuffer.remaining() >= 2) - { - mBuffer.put(inphase); - mBuffer.put(quadrature); - - mTimestampManager.increment(1); - - if(!mBuffer.hasRemaining()) - { - flush(); - } - } - } - - /** - * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered - * listener. - * - * @param samples to assemble - */ - public void receive(float[] samples) - { - if(mBuffer.remaining() >= samples.length) - { - mBuffer.put(samples); - mTimestampManager.increment(samples.length / 2); - - if(!mBuffer.hasRemaining()) - { - flush(); - } - } - else - { - int offset = 0; - - while(offset < samples.length) - { - int toCopy = mBuffer.remaining(); - - if((samples.length - offset) < toCopy) - { - toCopy = samples.length - offset; - } - - mBuffer.put(samples, offset, toCopy); - - offset += toCopy; - mTimestampManager.increment(toCopy / 2); - - if(!mBuffer.hasRemaining()) - { - flush(); - } - } - } - } - - /** - * Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered - * listener. - */ - public void receive(ReusableComplexBuffer samplesBuffer) - { - receive(samplesBuffer.getSamples()); - samplesBuffer.decrementUserCount(); - } - - /** - * Flushes the current buffer contents to the registered listener - */ - public void flush() - { - Listener listener = mListener; - - if(listener != null && mBuffer.position() > 0) - { - mBuffer.rewind(); - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(mBufferSize); - reusableComplexBuffer.reloadFrom(mBuffer, mCurrentBufferTimestamp); - listener.receive(reusableComplexBuffer); - } - - mBuffer.rewind(); - mCurrentBufferTimestamp = mTimestampManager.getCurrentTimestamp(); - } - - /** - * Sets the listener to receive the output from this buffer assembler. Note: the listener is responsible for - * managing the user count for any buffers received from this buffer assembler. - */ - public void setListener(Listener listener) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java deleted file mode 100644 index 40057c30..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferQueue.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * ***************************************************************************** - * 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 - * **************************************************************************** - */ -package io.github.dsheirer.sample.buffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ReusableComplexBufferQueue extends AbstractReusableBufferQueue -{ - private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexBufferQueue.class); - - /** - * Queue for managing the creation and reuse of reusable complex sample buffers. - * @param debugName to associate with this queue instance - */ - public ReusableComplexBufferQueue(String debugName) - { - super(debugName); - } - - /** - * Queue for managing the creation and reuse of reusable complex sample buffers. - */ - public ReusableComplexBufferQueue() - { - } - - /** - * Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no - * buffers available for reuse. - * - * @param size of the samples that will be loaded into the buffer - * @return a reusable buffer - */ - public ReusableComplexBuffer getBuffer(int size) - { - ReusableComplexBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableComplexBuffer(this, new float[size]); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - - buffer.resize(size); - buffer.incrementUserCount(); - - return buffer; - } - - /** - * Returns a buffer loaded with the samples. - * @param samples to load - * @param timestamp to apply - * @return buffer - */ - public ReusableComplexBuffer getBuffer(float[] samples, long timestamp) - { - ReusableComplexBuffer buffer = getRecycledBuffer(); - - if(buffer == null) - { - buffer = new ReusableComplexBuffer(this, samples); - buffer.setTimestamp(timestamp); - buffer.setDebugName("Owner:" + getDebugName()); - incrementBufferCount(); - } - else - { - buffer.reloadFrom(samples, timestamp); - } - - buffer.incrementUserCount(); - - return buffer; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java deleted file mode 100644 index ba66b323..00000000 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableFloatBuffer.java +++ /dev/null @@ -1,140 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.buffer; - -import org.apache.commons.lang3.Validate; - -import java.nio.FloatBuffer; - -public class ReusableFloatBuffer extends AbstractReusableBuffer -{ - private float[] mSamples; - - /** - * Creates a reusable, timestamped complex buffer using the specified time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers/users are finished using the buffer - * @param samples of data - * @param timestamp in millis for the buffer - */ - ReusableFloatBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples, long timestamp) - { - super(bufferDisposedListener, timestamp); - mSamples = samples; - - Validate.notNull(bufferDisposedListener, "Reusable Buffer Listener cannot be null"); - } - - /** - * Samples for this buffer - */ - public float[] getSamples() - { - return mSamples; - } - - /** - * Creates a copy of the samples from this buffer - */ - public float[] getSamplesCopy() - { - float[] samples = getSamples(); - float[] copy = new float[samples.length]; - System.arraycopy(samples, 0, copy, 0, samples.length); - return copy; - } - - /** - * Number of samples contained in this buffer - */ - public int getSampleCount() - { - return getSamples().length; - } - - /** - * Constructs a timestamped complex buffer using the current system time in milliseconds. - * - * NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this - * buffer should not modify the buffer contents. Each component should also increment the user count before - * sending this buffer to another component and should decrement the user count when finished using this buffer. - * - * @param bufferDisposedListener to be notified when all consumers are finished using the buffer - * @param samples of data - */ - public ReusableFloatBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples) - { - this(bufferDisposedListener, samples, System.currentTimeMillis()); - } - - /** - * Reloads this buffer by copying the sample array into this buffer. Internal buffer length will be automatically - * resized to fit the new sample array length. - * - * This is convenient if the source of the sample array originates via a native buffer. - * @param samples to copy into this buffer - * @param timestamp for the samples in millis - */ - public void reloadFrom(float[] samples, long timestamp) - { - if(mUserCount.get() > 0) - { - throw new IllegalStateException("New data cannot be loaded into this reusable buffer while the user count " + - "is above zero - user count:" + mUserCount.get()); - } - - mUserCount.set(0); - - resize(samples.length); - - System.arraycopy(samples, 0, mSamples, 0, samples.length); - - mTimestamp = timestamp; - } - - /** - * Reloads this buffer by filling this buffer from the float buffer (ie copying the buffer contents). Internal - * buffer length will be automatically resized to accommodate the float buffer's capacity/size. - * - * @param floatBuffer to copy into this buffer - * @param timestamp for the samples in millis - */ - public void reloadFrom(FloatBuffer floatBuffer, long timestamp) - { - resize(floatBuffer.capacity()); - floatBuffer.rewind(); - floatBuffer.get(mSamples); - - mTimestamp = timestamp; - } - - /** - * Resizes the internal array to the size argument - * @param size for the internal array - */ - protected void resize(int size) - { - if(mSamples.length != size) - { - mSamples = new float[size]; - } - } - -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java deleted file mode 100644 index f617b9a8..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleAssembler.java +++ /dev/null @@ -1,72 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.complex; - -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.Provider; - -import java.util.ArrayList; -import java.util.List; - -public class ComplexSampleAssembler implements Listener, - Provider> -{ - private ArrayList mBuffer; - private int mBufferSize; - private Listener> mListener; - - public ComplexSampleAssembler( int bufferSize ) - { - mBufferSize = bufferSize; - mBuffer = new ArrayList<>(); - } - - public void dispose() - { - mBuffer.clear(); - mListener = null; - } - - @Override - public void receive( Complex sample ) - { - mBuffer.add( sample ); - - if( mBuffer.size() >= mBufferSize ) - { - if( mListener != null ) - { - mListener.receive( mBuffer ); - } - - mBuffer = new ArrayList<>(); - } - } - - @Override - public void setListener( Listener> listener ) - { - mListener = listener; - } - - @Override - public void removeListener( Listener> listener ) - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java deleted file mode 100644 index 08c8ba09..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleBroadcaster.java +++ /dev/null @@ -1,76 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.complex; - -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Broadcasts received float samples to multiple listeners - */ -public class ComplexSampleBroadcaster implements ComplexSampleListener -{ - private CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList(); - - @Override - public void receive( float i, float q ) - { - broadcast( i, q ); - } - - /** - * Clear listeners to prepare for garbage collection - */ - public void dispose() - { - mListeners.clear(); - } - - public boolean hasListeners() - { - return !mListeners.isEmpty(); - } - - public int getListenerCount() - { - return mListeners.size(); - } - - public void addListener( ComplexSampleListener listener ) - { - mListeners.add( listener ); - } - - public void removeListener( ComplexSampleListener listener ) - { - mListeners.remove( listener ); - } - - public void clear() - { - mListeners.clear(); - } - - public void broadcast( float i, float q ) - { - for( ComplexSampleListener listener: mListeners ) - { - listener.receive( i, q ); - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java deleted file mode 100644 index 392f36ab..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleListListener.java +++ /dev/null @@ -1,25 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.complex; - -import java.util.List; - -public interface ComplexSampleListListener -{ - public void receive( List samples ); -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java deleted file mode 100644 index 63e5a8ab..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexSampleUtils.java +++ /dev/null @@ -1,51 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.complex; - - -import org.apache.commons.math3.util.FastMath; - -public class ComplexSampleUtils -{ - - public static Complex multiply( Complex a, - Complex b ) - { - return new Complex( - ( a.inphase() * b.inphase() ) - ( a.quadrature() * b.quadrature() ), - ( ( a.quadrature() * b.inphase() ) + ( a.inphase() * b.quadrature() ) ) ); - } - - public static Complex minus( Complex a, Complex b ) - { - return new Complex( a.inphase() - b.inphase(), - a.quadrature() - b.quadrature() ); - } - - public static double magnitude( Complex sample ) - { - return FastMath.sqrt( magnitudeSquared( sample ) ); - } - - public static int magnitudeSquared( Complex sample ) - { - return (int)( ( sample.inphase() * sample.inphase() ) + - ( sample.quadrature() * sample.quadrature() ) ); - } - -} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java new file mode 100644 index 00000000..9d6fb0cd --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamples.java @@ -0,0 +1,44 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.sample.complex; + +/** + * Wrapper for a complex sample array where I and Q samples are in separate arrays. + */ +public record ComplexSamples(float[] i, float[] q) +{ + /** + * Converts this non-interleaved complex samples to interleaved. + * @return interleaved samples. + */ + public InterleavedComplexSamples toInterleaved() + { + float[] interleaved = new float[i().length * 2]; + + for(int x = 0; x < i().length; x++) + { + interleaved[2 * x] = i()[x]; + interleaved[2 * x + 1] = q()[x]; + } + + return new InterleavedComplexSamples(interleaved, System.currentTimeMillis()); + } +} + diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java new file mode 100644 index 00000000..1164e3e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/ComplexSamplesRepackager.java @@ -0,0 +1,95 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.sample.complex; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Processes I & Q sample arrays into uniform length complex samples buffers. + */ +public class ComplexSamplesRepackager +{ + private int mSize; + private float[] mIResidual = new float[0]; + private float[] mQResidual = new float[0]; + + /** + * Constructs an instance. + * @param size or length of the output complex samples. + */ + public ComplexSamplesRepackager(int size) + { + mSize = size; + } + + /** + * Combines the I & Q sample arrays with any residual leftover from the previous invocation and + * chunks the arrays up into uniform length complex sample buffers, storing any leftover samples + * as residual to use in the next processing invocation. If the length of the sample arguments + * combined with any residual is not enough to create at least one buffer, those samples will be + * added to the residual and used in the next operation, and no complex sample buffer will be + * returned. + * + * @param i samples array to repackage + * @param q samples array to repackage + * @return zero or more complex samples of uniform length. + */ + public List process(float[] i, float[] q) + { + float[] iCombined = new float[mIResidual.length + i.length]; + float[] qCombined = new float[mQResidual.length + q.length]; + + if(mIResidual.length > 0) + { + System.arraycopy(mIResidual, 0, iCombined, 0, mIResidual.length); + System.arraycopy(mQResidual, 0, qCombined, 0, mQResidual.length); + } + + System.arraycopy(i, 0, iCombined, mIResidual.length, i.length); + System.arraycopy(q, 0, qCombined, mQResidual.length, q.length); + + int processed = 0; + + List samples = new ArrayList<>(); + + while((processed + mSize) < iCombined.length) + { + float[] iChunk = Arrays.copyOfRange(iCombined, processed, processed + mSize); + float[] qChunk = Arrays.copyOfRange(qCombined, processed, processed + mSize); + samples.add(new ComplexSamples(iChunk, qChunk)); + processed += mSize; + } + + if(processed < iCombined.length) + { + mIResidual = Arrays.copyOfRange(iCombined, processed, iCombined.length); + mQResidual = Arrays.copyOfRange(qCombined, processed, qCombined.length); + } + else + { + mIResidual = new float[0]; + mQResidual = new float[0]; + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesListener.java similarity index 86% rename from src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java rename to src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesListener.java index 15596a07..2527613d 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableBufferListener.java +++ b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesListener.java @@ -13,11 +13,11 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.sample.buffer; +package io.github.dsheirer.sample.complex; import io.github.dsheirer.sample.Listener; -public interface IReusableBufferListener +public interface IComplexSamplesListener { - Listener getReusableBufferListener(); + Listener getComplexSamplesListener(); } diff --git a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferProvider.java b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesProvider.java similarity index 84% rename from src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferProvider.java rename to src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesProvider.java index e84db05c..c91aeb62 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/IReusableByteBufferProvider.java +++ b/src/main/java/io/github/dsheirer/sample/complex/IComplexSamplesProvider.java @@ -13,21 +13,21 @@ * If not, see * ******************************************************************************/ -package io.github.dsheirer.sample.buffer; +package io.github.dsheirer.sample.complex; import io.github.dsheirer.sample.Listener; -public interface IReusableByteBufferProvider +public interface IComplexSamplesProvider { /** * Adds the listener to receive complex buffer samples */ - void setBufferListener(Listener listener); + void addBufferListener(Listener listener); /** * Removes the listener from receiving complex buffer samples */ - void removeBufferListener(Listener listener); + void removeBufferListener(Listener listener); /** * Indicates if there are any complex buffer listeners registered diff --git a/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java new file mode 100644 index 00000000..e0fc0d05 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesListener.java @@ -0,0 +1,29 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.complex; + +import io.github.dsheirer.sample.Listener; + +/** + * Interface for listener of interleaved complex samples + */ +public interface IInterleavedComplexSamplesListener +{ + Listener getReusableComplexBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java new file mode 100644 index 00000000..80e02501 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/IInterleavedComplexSamplesProvider.java @@ -0,0 +1,42 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.complex; + +import io.github.dsheirer.sample.Listener; + +/** + * Interface for a provider of interleaved complex samples + */ +public interface IInterleavedComplexSamplesProvider +{ + /** + * Adds the listener to receive interleaved complex samples + */ + void addBufferListener(Listener listener); + + /** + * Removes the listener from receiving interleaved complex samples + */ + void removeBufferListener(Listener listener); + + /** + * Indicates if there are any complex buffer listeners registered + */ + boolean hasBufferListeners(); +} diff --git a/src/main/java/io/github/dsheirer/sample/complex/InterleavedComplexSamples.java b/src/main/java/io/github/dsheirer/sample/complex/InterleavedComplexSamples.java new file mode 100644 index 00000000..d08ac31b --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/InterleavedComplexSamples.java @@ -0,0 +1,46 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.sample.complex; + +import io.github.dsheirer.sample.buffer.ITimestamped; + +/** + * Complex samples array that incorporates a timestamp for the first complex sample. + */ +public record InterleavedComplexSamples(float[] samples, long timestamp) implements ITimestamped +{ + /** + * Converts this interleaved complex samples to de-interleaved. + * @return de-interleaved samples. + */ + public ComplexSamples toDeinterleaved() + { + float[] i = new float[samples().length / 2]; + float[] q = new float[samples().length / 2]; + + for(int x = 0; x < i.length; x++) + { + i[x] = samples()[x / 2]; + q[x] = samples()[x / 2 + 1]; + } + + return new ComplexSamples(i, q); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java b/src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java deleted file mode 100644 index feea8bd9..00000000 --- a/src/main/java/io/github/dsheirer/sample/complex/ThreadKiller.java +++ /dev/null @@ -1,33 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.complex; - -/** - * Poison Pill class to kill a processing thread. Where a thread accepts - * ComplexSample objects, send this class to the thread as a sentinal value - * to kill the processing thread. - */ -public class ThreadKiller extends Complex -{ - private static final long serialVersionUID = 1L; - - public ThreadKiller() - { - super( 0, 0 ); - } -} diff --git a/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java b/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java deleted file mode 100644 index b9445ddf..00000000 --- a/src/main/java/io/github/dsheirer/sample/decimator/ComplexDecimator.java +++ /dev/null @@ -1,75 +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 - * - ******************************************************************************/ -package io.github.dsheirer.sample.decimator; - -import io.github.dsheirer.sample.complex.ComplexSampleListener; - -/** - * Decimator for complex samples - */ -public class ComplexDecimator implements ComplexSampleListener -{ - private ComplexSampleListener mListener; - private int mCounter = 0; - private int mDecimationRate; - - /** - * Constructs a new Decimator object with the specified decimation rate. - */ - public ComplexDecimator(int rate) - { - mDecimationRate = rate; - } - - public void dispose() - { - mListener = null; - } - - /** - * Sets a new decimation rate. Rate can be changed after this object - * is constructed. - */ - public synchronized void setDecimationRate(int rate) - { - mDecimationRate = rate; - } - - /** - * Receives samples allowing only 1 of every (rate) sample to go on - * to the registered listener - */ - @Override - public void receive(float i, float q) - { - mCounter++; - - if(mCounter >= mDecimationRate) - { - mListener.receive(i, q); - - mCounter = 0; - } - } - - /** - * Sets the decimated output listener - */ - public void setListener(ComplexSampleListener listener) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/decimator/Decimator.java b/src/main/java/io/github/dsheirer/sample/decimator/Decimator.java deleted file mode 100644 index 2cddce54..00000000 --- a/src/main/java/io/github/dsheirer/sample/decimator/Decimator.java +++ /dev/null @@ -1,72 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.decimator; - -import io.github.dsheirer.sample.Listener; - - -public class Decimator implements Listener -{ - private int mDecimationFactor; - private int mDecimationPointer = 0; - private Listener mListener; - - /** - * Constructs the class to consume samples and decimate them by the - * DECIMATIONFACTOR. - * - * @param decimationFactor - number of samples to decimate. Provides one - * sample to registered listeners and discards the next decimationFactor - 1 - * samples. - */ - public Decimator( int decimationFactor ) - { - mDecimationFactor = decimationFactor; - } - - /** - * Receives and broadcasts a sample and then discards the next - * decimationFactor -1 samples - */ - public void receive( T t ) - { - if( mDecimationPointer == 0 ) - { - if( mListener != null ) - { - mListener.receive( t ); - } - } - - mDecimationPointer++; - - //Reset the decimation pointer once it equals the decimation factor - if( mDecimationPointer == mDecimationFactor ) - { - mDecimationPointer = 0; - } - } - - /** - * Adds a sample listener to receive samples - */ - public void setListener( Listener listener ) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java b/src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java deleted file mode 100644 index c81ee3a5..00000000 --- a/src/main/java/io/github/dsheirer/sample/decimator/RealDecimator.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dsheirer.sample.decimator; - -import io.github.dsheirer.sample.real.RealSampleListener; - - -public class RealDecimator implements RealSampleListener -{ - private RealSampleListener mListener; - private int mCounter = 0; - private int mDecimationRate; - - /** - * Constructs a new Decimator object with the specified decimation rate. - */ - public RealDecimator( int rate ) - { - mDecimationRate = rate; - } - - public void dispose() - { - mListener = null; - } - - /** - * Sets a new decimation rate. Rate can be changed after this object - * is constructed. - */ - public synchronized void setDecimationRate( int rate ) - { - mDecimationRate = rate; - } - - /** - * Receives samples allowing only 1 of every (rate) sample to go on - * to the registered listener - */ - @Override - public void receive( float sample ) - { - mCounter++; - - if( mCounter >= mDecimationRate ) - { - mListener.receive( sample ); - - mCounter = 0; - } - } - - /** - * Sets the decimated output listener - */ - public void setListener( RealSampleListener listener ) - { - mListener = listener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java b/src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java new file mode 100644 index 00000000..57c95563 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/real/IRealBufferListener.java @@ -0,0 +1,26 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.real; + +import io.github.dsheirer.sample.Listener; + +public interface IRealBufferListener +{ + Listener getBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java b/src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java new file mode 100644 index 00000000..aef2b002 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/real/IRealBufferProvider.java @@ -0,0 +1,34 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.sample.real; + +import io.github.dsheirer.sample.Listener; + +public interface IRealBufferProvider +{ + /** + * Adds the listener to receive complex buffer samples + */ + void setBufferListener(Listener listener); + + /** + * Removes the listener from receiving complex buffer samples + */ + void removeBufferListener(); +} diff --git a/src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java b/src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java deleted file mode 100644 index b33ba053..00000000 --- a/src/main/java/io/github/dsheirer/sample/real/RealSampleBroadcaster.java +++ /dev/null @@ -1,76 +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 - ******************************************************************************/ -package io.github.dsheirer.sample.real; - -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Broadcasts a received float sample to multiple listeners - */ -public class RealSampleBroadcaster implements RealSampleListener -{ - private CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList(); - - @Override - public void receive( float sample ) - { - broadcast( sample ); - } - - /** - * Clear listeners to prepare for garbage collection - */ - public void dispose() - { - mListeners.clear(); - } - - public boolean hasListeners() - { - return !mListeners.isEmpty(); - } - - public int getListenerCount() - { - return mListeners.size(); - } - - public void addListener( RealSampleListener listener ) - { - mListeners.add( listener ); - } - - public void removeListener( RealSampleListener listener ) - { - mListeners.remove( listener ); - } - - public void clear() - { - mListeners.clear(); - } - - public void broadcast( float sample ) - { - for( RealSampleListener listener: mListeners ) - { - listener.receive( sample ); - } - } -} diff --git a/src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java b/src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java deleted file mode 100644 index 71cc4086..00000000 --- a/src/main/java/io/github/dsheirer/sample/real/RealSampleProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.dsheirer.sample.real; - -public interface RealSampleProvider -{ - public void setListener( RealSampleListener listener ); - - public void removeListener( RealSampleListener listener ); -} diff --git a/src/main/java/io/github/dsheirer/source/ComplexSource.java b/src/main/java/io/github/dsheirer/source/ComplexSource.java index 098641d5..30afee74 100644 --- a/src/main/java/io/github/dsheirer/source/ComplexSource.java +++ b/src/main/java/io/github/dsheirer/source/ComplexSource.java @@ -17,9 +17,9 @@ package io.github.dsheirer.source; import io.github.dsheirer.sample.Provider; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; -public abstract class ComplexSource extends Source implements Provider +public abstract class ComplexSource extends Source implements Provider { @Override public SampleType getSampleType() diff --git a/src/main/java/io/github/dsheirer/source/RealSource.java b/src/main/java/io/github/dsheirer/source/RealSource.java index a84ad915..47f0d57c 100644 --- a/src/main/java/io/github/dsheirer/source/RealSource.java +++ b/src/main/java/io/github/dsheirer/source/RealSource.java @@ -1,11 +1,29 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + package io.github.dsheirer.source; import io.github.dsheirer.sample.Provider; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -public abstract class RealSource extends Source implements Provider +public abstract class RealSource extends Source implements Provider { @Override public SampleType getSampleType() diff --git a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java index 6ceab8ad..b1425004 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java +++ b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java @@ -1,23 +1,26 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.mixer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.ComplexSampleAdapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.adapter.ISampleAdapter; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +31,7 @@ import javax.sound.sampled.TargetDataLine; public class ComplexMixer { private final static Logger mLog = LoggerFactory.getLogger(ComplexMixer.class); - private MixerReader mMixerReader; + private MixerReader mMixerReader; private String mName; /** @@ -38,12 +41,11 @@ public class ComplexMixer * @param targetDataLine - mixer or sound card to be used * @param format - audio format * @param name - token name to use for this mixer - * @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ReusableComplexBuffer. + * @param sampleAdapter - adapter to convert byte array data read from the mixer into ReusableComplexBuffer. */ - public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name, - ComplexSampleAdapter complexSampleAdapter) + public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name, ISampleAdapter sampleAdapter) { - mMixerReader = new MixerReader<>(format, targetDataLine, complexSampleAdapter); + mMixerReader = new MixerReader(format, targetDataLine, sampleAdapter); mName = name; } @@ -74,7 +76,7 @@ public class ComplexMixer mMixerReader.stop(); } - public void setBufferListener(Listener listener) + public void setBufferListener(Listener listener) { mMixerReader.setBufferListener(listener); } diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java index a68012e7..2379ede3 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.mixer; import io.github.dsheirer.audio.AudioFormats; @@ -84,7 +87,7 @@ public class MixerManager if(dataLine != null) { return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, - new RealShortAdapter("RealMixerSource - Mono")); + new RealShortAdapter()); } } catch(LineUnavailableException e) @@ -108,7 +111,7 @@ public class MixerManager if(dataLine != null) { return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO, - new RealChannelShortAdapter(mixerConfig.getChannel(), "RealMixerSource-Stereo")); + new RealChannelShortAdapter(mixerConfig.getChannel())); } } catch(LineUnavailableException e) diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java index 351d9a4c..3c560828 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.AbstractSampleAdapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.adapter.ISampleAdapter; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.heartbeat.HeartbeatManager; import io.github.dsheirer.util.ThreadPool; @@ -37,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * the samples to an array of floats using the specified adapter. Dispatches float arrays to the registered * buffer listener. */ -public class MixerReader implements Runnable +public class MixerReader implements Runnable { private final static Logger mLog = LoggerFactory.getLogger(MixerReader.class); @@ -48,14 +50,14 @@ public class MixerReader implements Runnable private AtomicBoolean mRunning = new AtomicBoolean(); private ScheduledFuture mScheduledFuture; private int mBytesRead; - private AbstractSampleAdapter mSampleAdapter; - private Listener mReusableBufferListener; + private ISampleAdapter mSampleAdapter; + private Listener mListener; private Listener mSourceEventListener; private AudioFormat mAudioFormat; private HeartbeatManager mHeartbeatManager; public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, - AbstractSampleAdapter abstractSampleAdapter, HeartbeatManager heartbeatManager) + ISampleAdapter abstractSampleAdapter, HeartbeatManager heartbeatManager) { mTargetDataLine = targetDataLine; mAudioFormat = audioFormat; @@ -66,7 +68,7 @@ public class MixerReader implements Runnable mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize(); } - public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, AbstractSampleAdapter abstractSampleAdapter) + public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, ISampleAdapter abstractSampleAdapter) { this(audioFormat, targetDataLine, abstractSampleAdapter, new HeartbeatManager()); } @@ -210,9 +212,9 @@ public class MixerReader implements Runnable //Sample adapter automatically sets initial listener count to one. T reusableBuffer = mSampleAdapter.convert(mBuffer); - if(reusableBuffer != null && mReusableBufferListener != null) + if(reusableBuffer != null && mListener != null) { - mReusableBufferListener.receive(reusableBuffer); + mListener.receive(reusableBuffer); } } else if(mBytesRead > 0) @@ -220,9 +222,9 @@ public class MixerReader implements Runnable //Sample adapter automatically sets initial listener count to one. T reusableBuffer = mSampleAdapter.convert(Arrays.copyOf(mBuffer, mBytesRead)); - if(reusableBuffer != null && mReusableBufferListener != null) + if(reusableBuffer != null && mListener != null) { - mReusableBufferListener.receive(reusableBuffer); + mListener.receive(reusableBuffer); } } } @@ -247,7 +249,7 @@ public class MixerReader implements Runnable */ public void setBufferListener(Listener listener) { - mReusableBufferListener = listener; + mListener = listener; } /** @@ -255,7 +257,7 @@ public class MixerReader implements Runnable */ public void removeBufferListener() { - mReusableBufferListener = null; + mListener = null; } /** @@ -296,7 +298,7 @@ public class MixerReader implements Runnable { stop(); mHeartbeatManager = null; - mReusableBufferListener = null; + mListener = null; mSourceEventListener = null; } } diff --git a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java index 6232cc39..85edcd7a 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java +++ b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java @@ -1,23 +1,25 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.AbstractSampleAdapter; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.adapter.ISampleAdapter; import io.github.dsheirer.source.RealSource; import io.github.dsheirer.source.SourceEvent; @@ -40,7 +42,7 @@ public class RealMixerSource extends RealSource * @param sampleAdapter - adapter to convert byte array data read from the * mixer into float array data. */ - public RealMixerSource(TargetDataLine targetDataLine, AudioFormat format, AbstractSampleAdapter sampleAdapter) + public RealMixerSource(TargetDataLine targetDataLine, AudioFormat format, ISampleAdapter sampleAdapter) { mMixerReader = new MixerReader(format, targetDataLine, sampleAdapter, getHeartbeatManager()); } @@ -101,7 +103,7 @@ public class RealMixerSource extends RealSource /** * Sets the listener to receive sample data in reusable buffers. */ - public void setListener(Listener listener) + public void setListener(Listener listener) { mMixerReader.setBufferListener(listener); } @@ -110,7 +112,7 @@ public class RealMixerSource extends RealSource * Removes the listener from receiving sample data. * @param listener */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mMixerReader.removeBufferListener(); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java index e5de2e64..12a29117 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java @@ -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 @@ -18,13 +18,13 @@ */ package io.github.dsheirer.source.tuner; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.INativeBufferProvider; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderFactory; -import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.NativeBufferWaveRecorder; +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.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -40,16 +40,16 @@ import org.slf4j.LoggerFactory; import java.util.SortedSet; public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener, - IReusableComplexBufferProvider, Listener, ITunerErrorListener + INativeBufferProvider, Listener, ITunerErrorListener { private final static Logger mLog = LoggerFactory.getLogger(TunerController.class); - protected ReusableBufferBroadcaster mReusableBufferBroadcaster = new ReusableBufferBroadcaster(); + protected Broadcaster mNativeBufferBroadcaster = new Broadcaster(); protected FrequencyController mFrequencyController; private int mMiddleUnusableHalfBandwidth; private double mUsableBandwidthPercentage; private Listener mSourceEventListener; private int mMeasuredFrequencyError; - private ComplexBufferWaveRecorder mRecorder; + private NativeBufferWaveRecorder mRecorder; private ITunerErrorListener mTunerErrorListener; /** @@ -137,13 +137,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, case REQUEST_START_SAMPLE_STREAM: if(sourceEvent.getSource() instanceof Listener) { - addBufferListener((Listener)sourceEvent.getSource()); + addBufferListener((Listener)sourceEvent.getSource()); } break; case REQUEST_STOP_SAMPLE_STREAM: if(sourceEvent.getSource() instanceof Listener) { - removeBufferListener((Listener)sourceEvent.getSource()); + removeBufferListener((Listener)sourceEvent.getSource()); } break; default: @@ -357,18 +357,18 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, * Adds the listener to receive complex buffer samples */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { - mReusableBufferBroadcaster.addListener(listener); + mNativeBufferBroadcaster.addListener(listener); } /** * Removes the listener from receiving complex buffer samples */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { - mReusableBufferBroadcaster.removeListener(listener); + mNativeBufferBroadcaster.removeListener(listener); } /** @@ -377,24 +377,24 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, @Override public boolean hasBufferListeners() { - return mReusableBufferBroadcaster.hasListeners(); + return mNativeBufferBroadcaster.hasListeners(); } /** * Broadcasts the buffer to any registered listeners */ - protected void broadcast(ReusableComplexBuffer reusableComplexBuffer) + protected void broadcast(INativeBuffer complexSamples) { - mReusableBufferBroadcaster.broadcast(reusableComplexBuffer); + mNativeBufferBroadcaster.broadcast(complexSamples); } /** * Implements the Listener interface to receive and distribute complex buffers from subclass implementations */ @Override - public void receive(ReusableComplexBuffer complexBuffer) + public void receive(INativeBuffer nativeBuffer) { - broadcast(complexBuffer); + broadcast(nativeBuffer); } /** @@ -480,13 +480,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, /** * Records the complex I/Q buffers produced by the tuner - * @param recorderManager to obtain a baseband recorder + * @param userPreferences to obtain a baseband recorder */ public void startRecorder(UserPreferences userPreferences) { if(!isRecording()) { - mRecorder = RecorderFactory.getBasebandRecorder("TUNER_" + getFrequency(), userPreferences); + mRecorder = RecorderFactory.getTunerRecorder("TUNER_" + getFrequency(), userPreferences); mRecorder.setSampleRate((float)getSampleRate()); mRecorder.start(); addBufferListener(mRecorder); diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java deleted file mode 100644 index eb40a07a..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java +++ /dev/null @@ -1,160 +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 - * - ******************************************************************************/ -package io.github.dsheirer.source.tuner.airspy; - -import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter; -import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; - -public class AirspySampleConverter extends NativeBufferConverter -{ - private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; - - private DCRemovalFilter mDCFilter = new DCRemovalFilter(0.01f); - private HilbertTransform mHilbertTransform = new HilbertTransform(); - private boolean mSamplePacking = false; - private FloatBuffer mFloatBuffer; - private float[] mConvertedSamples; - - /** - * Adapter to translate byte buffers received from the airspy tuner into - * float buffers for processing. - */ - public AirspySampleConverter() - { - } - - @Override - protected FloatBuffer convertSamples(ByteBuffer buffer, int length) - { - float[] samples = convert(buffer); - - if(mFloatBuffer == null || mFloatBuffer.capacity() != samples.length) - { - mFloatBuffer = FloatBuffer.allocate(samples.length); - } - - mFloatBuffer.rewind(); - mFloatBuffer.put(samples); - - return mFloatBuffer; - } - - /** - * 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; - } - - private float[] convert(ByteBuffer samples) - { - if(mSamplePacking) - { - convertPacked(samples.order(ByteOrder.LITTLE_ENDIAN)); - } - else - { - convertUnpacked(samples); - } - - mDCFilter.filter(mConvertedSamples); - - return mHilbertTransform.filter(mConvertedSamples); - } - - /** - * Converts the byte array containing unsigned 12-bit short values into - * signed float values in the range -1 to 1; - * - * @param buffer - native byte buffer containing unsigned 16-bit values - */ - private void convertUnpacked(ByteBuffer buffer) - { - buffer.rewind(); - - if(mConvertedSamples == null || mConvertedSamples.length != buffer.capacity() / 2) - { - mConvertedSamples = new float[buffer.capacity() / 2]; - } - - int pointer = 0; - - - while(buffer.remaining() >= 2) - { - byte lsb = buffer.get(); - byte msb = buffer.get(); - mConvertedSamples[pointer++] = scale((lsb & 0xFF) | (msb << 8)); - } - } - - /** - * Converts every 3 bytes containing a pair of 12-bit unsigned values into - * a pair of float values in the range -1 to 1; - * - * @param buffer - native byte buffer containing packet 12-bit unsigned samples - */ - private void convertPacked(ByteBuffer buffer) - { - buffer.rewind(); - - int sampleCount = buffer.capacity() / 3 * 2; - - if(mConvertedSamples == null || mConvertedSamples.length != sampleCount) - { - mConvertedSamples = new float[sampleCount]; - } - - int pointer = 0; - - byte b1; - byte b2; - byte b3; - int first; - int second; - - while(buffer.remaining() >= 3) - { - b1 = buffer.get(); - b2 = buffer.get(); - b3 = buffer.get(); - - first = ((b1 << 4) & 0xFF0) | ((b2 >> 4) & 0xF); - mConvertedSamples[pointer++] = scale(first); - - second = ((b2 << 8) & 0xF00) | (b3 & 0xFF); - mConvertedSamples[pointer++] = scale(second); - } - } - - /** - * Converts unsigned 12-bit values to signed 12-bit values and then scales - * the signed value to a signed float value in range: -1.0 : +1.0 - */ - public static float scale(int value) - { - return (float) ((value & 0xFFF) - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT; - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java index c10bfff4..cea290f5 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java @@ -1,27 +1,25 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.airspy; +import io.github.dsheirer.buffer.airspy.AirspyNativeBufferFactory; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; @@ -76,7 +74,7 @@ public class AirspyTunerController extends USBTunerController private Device mDevice; private DeviceHandle mDeviceHandle; - private AirspySampleConverter mSampleAdapter = new AirspySampleConverter(); + private AirspyNativeBufferFactory mNativeBufferFactory = new AirspyNativeBufferFactory(); private AirspyDeviceInformation mDeviceInfo; private List mSampleRates = new ArrayList<>(); private int mSampleRate = 0; @@ -166,7 +164,7 @@ public class AirspyTunerController extends USBTunerController String deviceName = "Airspy " + getDeviceInfo().getSerialNumber(); - mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter, + mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE, this); } @@ -489,7 +487,7 @@ public class AirspyTunerController extends USBTunerController /* If we didn't throw an exception above, then update the sample adapter * to process samples accordingly */ - mSampleAdapter.setSamplePacking(enabled); + mNativeBufferFactory.setSamplePacking(enabled); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java index 9253200b..3861b143 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java @@ -1,33 +1,36 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.channel; -import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; +import io.github.dsheirer.dsp.filter.cic.PrimeCicDecimationFilter; import io.github.dsheirer.dsp.filter.design.FilterDesignException; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; -import io.github.dsheirer.sample.IOverflowListener; +import io.github.dsheirer.dsp.mixer.ComplexMixer; +import io.github.dsheirer.dsp.mixer.ComplexMixerFactory; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.complex.Complex; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.ComplexSamplesRepackager; import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; -import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -35,24 +38,24 @@ import java.util.List; * CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing * occurs on a scheduled runnable thread. */ -public class CICTunerChannelSource extends TunerChannelSource implements Listener +public class CICTunerChannelSource extends TunerChannelSource implements Listener { -// private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class); - //Maximum number of filled buffers for the blocking queue private static final int BUFFER_MAX_CAPACITY = 300; //Threshold for resetting buffer overflow condition private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100; - private OverflowableReusableBufferTransferQueue mBuffer; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource"); - private IOscillator mFrequencyCorrectionMixer; - private ComplexPrimeCICDecimate mDecimationFilter; - private List mSampleBuffers = new ArrayList<>(); + //Samples repackager is set to 2048 (a power of 2) to align with channel processing chain requirements. + private final ComplexSamplesRepackager mSamplesRepackager = new ComplexSamplesRepackager(1024); + private Dispatcher mBufferDispatcher; + private ComplexMixer mFrequencyCorrectionMixer; + private PrimeCicDecimationFilter mIDecimationFilter; + private PrimeCicDecimationFilter mQDecimationFilter; private double mChannelSampleRate; private long mChannelFrequencyCorrection = 0; private long mTunerFrequency; + private Listener mListener; /** * Constructs a frequency translating and CIC decimating channel source. @@ -71,29 +74,35 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene int decimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate()); - mDecimationFilter = new ComplexPrimeCICDecimate(sampleRate, decimation, channelSpecification.getPassFrequency(), - channelSpecification.getStopFrequency()); + mIDecimationFilter = new PrimeCicDecimationFilter(sampleRate, decimation, channelSpecification.getPassFrequency(), + channelSpecification.getStopFrequency()); + mQDecimationFilter = new PrimeCicDecimationFilter(sampleRate, decimation, channelSpecification.getPassFrequency(), + channelSpecification.getStopFrequency()); - mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD); + mBufferDispatcher = new Dispatcher(BUFFER_MAX_CAPACITY, "sdrtrunk cic channel " + + tunerChannel.getFrequency(), new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(new NativeBufferProcessor()); //Setup the frequency mixer to the current source frequency mChannelSampleRate = sampleRate / (double)decimation; mTunerFrequency = tunerChannel.getFrequency(); long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); - mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate); + mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(frequencyOffset, sampleRate); } - /** - * Registers an overflow listener to be notified in the internal buffer is in an - * overflow state. - * - * @param listener to receiver updates to the source overflow state - */ @Override - public void setOverflowListener(IOverflowListener listener) + public void start() { - mBuffer.setOverflowListener(listener); + super.start(); + mBufferDispatcher.start(); + } + + @Override + public void stop() + { + super.stop(); + mBufferDispatcher.stop(); } @Override @@ -106,9 +115,9 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene * Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated. */ @Override - public void receive(ReusableComplexBuffer buffer) + public void receive(T complexSamples) { - mBuffer.offer(buffer); + mBufferDispatcher.receive(complexSamples); } /** @@ -191,21 +200,14 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene /** * Sets the listener to receive the complex buffer sample output from this channel * - * @param complexBufferListener to receive complex buffers + * @param listener to receive complex buffers */ @Override - public void setListener(Listener complexBufferListener) + public void setListener(Listener listener) { - mDecimationFilter.setListener(complexBufferListener); + mListener = listener; } - @Override - public void removeListener(Listener listener) - { - mDecimationFilter.removeListener(); - } - - @Override public double getSampleRate() { @@ -213,37 +215,33 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene } /** - * Primary processing method that is invoked on a recurring basis to process any queued complex buffers. - * - * Mixes the target frequency to baseband and then passes the buffer to the CIC decimation filter + * Processes native buffers received from the dispatcher and sends to the registered listener */ - protected void processSamples() + public class NativeBufferProcessor implements Listener { - mBuffer.drainTo(mSampleBuffers); - - for(ReusableComplexBuffer complexBuffer : mSampleBuffers) + @Override + public void receive(T nativeBuffer) { - float[] samples = complexBuffer.getSamples(); + getHeartbeatManager().broadcast(); - ReusableComplexBuffer translatedComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - float[] translatedSamples = translatedComplexBuffer.getSamples(); - - /* Perform frequency translation */ - for(int x = 0; x < samples.length; x += 2) + if(mListener != null) { - mFrequencyCorrectionMixer.rotate(); + Iterator iterator = nativeBuffer.iterator(); - translatedSamples[x] = Complex.multiplyInphase(samples[x], samples[x + 1], - mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + while(iterator.hasNext()) + { + ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next()); + float[] i = mIDecimationFilter.decimate(basebanded.i()); + float[] q = mQDecimationFilter.decimate(basebanded.q()); - translatedSamples[x + 1] = Complex.multiplyQuadrature(samples[x], samples[x + 1], - mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature()); + List repackaged = mSamplesRepackager.process(i, q); + + for(ComplexSamples samples: repackaged) + { + mListener.receive(samples); + } + } } - - mDecimationFilter.receive(translatedComplexBuffer); - complexBuffer.decrementUserCount(); } - - mSampleBuffers.clear(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java new file mode 100644 index 00000000..f3ab04fe --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/HalfBandTunerChannelSource.java @@ -0,0 +1,244 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.source.tuner.channel; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; +import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +import io.github.dsheirer.dsp.mixer.ComplexMixer; +import io.github.dsheirer.dsp.mixer.ComplexMixerFactory; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.util.Dispatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; + +/** + * Complex sample source that provides a frequency-translated and decimated sample buffer stream. Uses a + * CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing + * occurs on a scheduled runnable thread. + */ +public class HalfBandTunerChannelSource extends TunerChannelSource implements Listener +{ + private static final Logger mLog = LoggerFactory.getLogger(HalfBandTunerChannelSource.class); + + //Maximum number of filled buffers for the blocking queue + private static final int BUFFER_MAX_CAPACITY = 600; + + private Dispatcher mBufferDispatcher; + private ComplexMixer mFrequencyCorrectionMixer; + private IRealDecimationFilter mIDecimationFilter; + private IRealDecimationFilter mQDecimationFilter; + private double mChannelSampleRate; + private long mChannelFrequencyCorrection = 0; + private long mTunerFrequency; + private Listener mListener; + + /** + * Constructs a frequency translating and CIC decimating channel source. + * + * @param producerSourceEventListener to receive sample stream start/stop requests + * @param tunerChannel that details the desired channel frequency and bandwidth + * @param sampleRate of the incoming sample stream + * @param channelSpecification for the requested channel. + * @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter + * designer and the filter parameters. + */ + public HalfBandTunerChannelSource(Listener producerSourceEventListener, TunerChannel tunerChannel, + double sampleRate, ChannelSpecification channelSpecification) throws FilterDesignException + { + super(producerSourceEventListener, tunerChannel); + + int desiredDecimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate()); + int decimation = DecimationFilterFactory.getDecimationRate(desiredDecimation); + + mIDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimation); + mQDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimation); + + mBufferDispatcher = new Dispatcher(BUFFER_MAX_CAPACITY, "sdrtrunk heterodyne channel " + + tunerChannel.getFrequency(), new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(new NativeBufferProcessor()); + + //Setup the frequency mixer to the current source frequency + mChannelSampleRate = sampleRate / (double)decimation; + mTunerFrequency = tunerChannel.getFrequency(); + long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); + mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(frequencyOffset, sampleRate); + } + + @Override + public void start() + { + super.start(); + mBufferDispatcher.start(); + } + + @Override + public void stop() + { + super.stop(); + mBufferDispatcher.stop(); + } + + @Override + public void dispose() + { + } + + /** + * Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated. + */ + @Override + public void receive(T complexSamples) + { + mBufferDispatcher.receive(complexSamples); + } + + /** + * Sets/updates the center frequency for the sample streaming being sent from the producer. + * + * @param frequency in hertz + */ + @Override + public void setFrequency(long frequency) + { + mTunerFrequency = frequency; + updateMixerFrequencyOffset(); + } + + /** + * Current frequency correction being applied to the channel sample stream + * + * @return correction in hertz + */ + public long getFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Changes the frequency correction value and broadcasts the change to the registered downstream listener. + * + * @param correction current frequency correction value. + */ + public void setFrequencyCorrection(long correction) + { + mChannelFrequencyCorrection = correction; + updateMixerFrequencyOffset(); + broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); + } + + /** + * Calculates the local mixer frequency offset from the tuned frequency, + * channel's requested frequency, and channel frequency correction. + */ + private void updateMixerFrequencyOffset() + { + long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; + mFrequencyCorrectionMixer.setFrequency(offset); + } + + /** + * Sets the sample rate of the incoming sample stream from the producer + * + * @param sampleRate in hertz + */ + @Override + protected void setSampleRate(double sampleRate) + { + //Not implemented. Sample rate changes are not permitted once sample stream starts + } + + /** + * Sets the frequency correction for the outbound sample stream to the consumer + * + * @param correction in hertz + */ + @Override + protected void setChannelFrequencyCorrection(long correction) + { + mChannelFrequencyCorrection = correction; + broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction)); + updateMixerFrequencyOffset(); + } + + /** + * Frequency correction value currently being applied to the outbound sample stream + */ + @Override + public long getChannelFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Sets the listener to receive the complex buffer sample output from this channel + * + * @param listener to receive complex buffers + */ + @Override + public void setListener(Listener listener) + { + if(listener == null) + { + //Wait for the native buffer processor thread to die before we nullify the listener + stop(); + } + + mListener = listener; + } + + @Override + public double getSampleRate() + { + return mChannelSampleRate; + } + + /** + * Processes native buffers received from the dispatcher and sends to the registered listener + */ + public class NativeBufferProcessor implements Listener + { + @Override + public void receive(T nativeBuffer) + { + getHeartbeatManager().broadcast(); + + Listener listener = mListener; + + if(listener != null) + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next()); + float[] i = mIDecimationFilter.decimateReal(basebanded.i()); + float[] q = mQDecimationFilter.decimateReal(basebanded.q()); + listener.receive(new ComplexSamples(i, q)); + } + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java index e81ac76f..bf84742d 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/MultiFrequencyTunerChannelSource.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,14 +14,14 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.source.tuner.channel; import com.google.common.eventbus.Subscribe; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; @@ -53,7 +52,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource private List mLockedFrequencies = new ArrayList<>(); private int mFrequencyListPointer = 0; private ChannelSpecification mChannelSpecification; - private Listener mReusableComplexBufferListener; + private Listener mComplexSamplesListener; private Listener mHeartbeatListener; private String mPreferredTuner; private AtomicBoolean mChangingChannels = new AtomicBoolean(); @@ -93,7 +92,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource { //Shutdown the existing tuner channel source mTunerChannelSource.stop(); - mTunerChannelSource.removeListener(mReusableComplexBufferListener); + mTunerChannelSource.setListener(null); mTunerChannelSource.removeSourceEventListener(); mTunerChannelSource.removeHeartbeatListener(mHeartbeatListener); mTunerChannelSource.dispose(); @@ -122,7 +121,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource { mTunerChannelSource = (TunerChannelSource)source; mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter); - mTunerChannelSource.setListener(mReusableComplexBufferListener); + mTunerChannelSource.setListener(mComplexSamplesListener); mTunerChannelSource.addHeartbeatListener(mHeartbeatListener); mTunerChannelSource.start(); mTunerChannel = nextChannel; @@ -295,36 +294,16 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource } @Override - protected void processSamples() + public void setListener(Listener complexSamplesListener) { - if(mTunerChannelSource != null) - { - mTunerChannelSource.processSamples(); - } - } - - @Override - public void setListener(Listener complexBufferListener) - { - mReusableComplexBufferListener = complexBufferListener; + mComplexSamplesListener = complexSamplesListener; if(mTunerChannelSource != null) { - mTunerChannelSource.setListener(complexBufferListener); + mTunerChannelSource.setListener(complexSamplesListener); } } - @Override - public void removeListener(Listener listener) - { - if(mTunerChannelSource != null) - { - mTunerChannelSource.removeListener(listener); - } - - mReusableComplexBufferListener = null; - } - @Override public double getSampleRate() { diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java index ae1c0898..064a2b12 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/PassThroughChannelSource.java @@ -1,46 +1,48 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2019 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public - * License as published by 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.channel; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.NativeBufferPoisonPill; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.TunerController; +import io.github.dsheirer.util.Dispatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; +import java.util.Iterator; /** * Pass-through channel source that simply passes complex sample buffers from the tuner controller * directly to the registered listener */ public class PassThroughChannelSource extends TunerChannelSource implements ISourceEventListener, - Listener + Listener { private final static Logger mLog = LoggerFactory.getLogger(PassThroughChannelSource.class); private TunerController mTunerController; - private OverflowableReusableBufferTransferQueue mBufferQueue = - new OverflowableReusableBufferTransferQueue<>(500, 100); - private List mBuffersToProcess = new ArrayList<>(); - private Listener mComplexBufferListener; + private Dispatcher mBufferDispatcher; + private Listener mComplexSamplesListener; /** * Constructs an instance @@ -54,6 +56,23 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou { super(listener, tunerChannel); mTunerController = tunerController; + mBufferDispatcher = new Dispatcher<>(500, "sdrtrunk pass-through channel " + + tunerChannel.getFrequency(), new NativeBufferPoisonPill()); + mBufferDispatcher.setListener(new BufferProcessor()); + } + + @Override + public void start() + { + super.start(); + mBufferDispatcher.start(); + } + + @Override + public void stop() + { + super.stop(); + mBufferDispatcher.stop(); } @Override @@ -81,35 +100,9 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou } @Override - public void setListener(Listener complexBufferListener) + public void setListener(Listener complexSamplesListener) { - mComplexBufferListener = complexBufferListener; - } - - @Override - public void removeListener(Listener listener) - { - mComplexBufferListener = null; - } - - @Override - protected void processSamples() - { - mBufferQueue.drainTo(mBuffersToProcess); - - for(ReusableComplexBuffer buffer: mBuffersToProcess) - { - if(mComplexBufferListener != null) - { - mComplexBufferListener.receive(buffer); - } - else - { - buffer.decrementUserCount(); - } - } - - mBuffersToProcess.clear(); + mComplexSamplesListener = complexSamplesListener; } @Override @@ -119,8 +112,27 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou } @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) + public void receive(INativeBuffer buffer) { - mBufferQueue.offer(reusableComplexBuffer); + mBufferDispatcher.receive(buffer); + } + + public class BufferProcessor implements Listener + { + @Override + public void receive(INativeBuffer nativeBuffer) + { + getHeartbeatManager().broadcast(); + + if(mComplexSamplesListener != null) + { + Iterator iterator = nativeBuffer.iterator(); + + while(iterator.hasNext()) + { + mComplexSamplesListener.receive(iterator.next()); + } + } + } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java index ad16d3ed..7d8dadae 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java @@ -1,40 +1,33 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.channel; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.ComplexSource; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter; import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - public abstract class TunerChannelSource extends ComplexSource implements ISourceEventProcessor { private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class); @@ -43,7 +36,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc protected TunerChannel mTunerChannel; private Listener mProducerSourceEventListener; private Listener mConsumerSourceEventListener; - private ScheduledIntervalProcessor mScheduledIntervalProcessor = new ScheduledIntervalProcessor(); +// private ScheduledIntervalProcessor mScheduledIntervalProcessor = new ScheduledIntervalProcessor(); /** * Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality @@ -99,15 +92,15 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc /** * Sets the listener to receive the complex buffer sample output from this channel - * @param complexBufferListener to receive complex buffers + * @param complexSamplesListener to receive complex buffers */ - public abstract void setListener(Listener complexBufferListener); + public abstract void setListener(Listener complexSamplesListener); /** * Commands sub-class to process queued samples and distribute them to the consumer. This method will be invoked * by an interval timer. */ - protected abstract void processSamples(); +// protected abstract void processSamples(); /** * Tuner channel for this tuner channel source @@ -125,7 +118,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc //Broadcast current frequency and sample rate so consumer can configure correctly broadcastConsumerSourceEvent(SourceEvent.frequencyChange(this, getFrequency(), "Startup")); broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this)); - mScheduledIntervalProcessor.start(); } /** @@ -136,7 +128,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc { broadcastProducerSourceEvent(SourceEvent.stopSampleStreamRequest(this)); broadcastProducerSourceEvent(SourceEvent.sourceDisposeRequest(this)); - mScheduledIntervalProcessor.stop(); } @Override @@ -261,94 +252,94 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc mConsumerSourceEventListener = null; } - /** - * Processor to periodically invoke buffer sample processing on an interval timer. At each interval this processor - * sends a heartbeat to the registered consumer and then commands the sub-class implementation to process any - * queued buffers and distribute complex buffer sample(s) to the registered consumer. - */ - public class ScheduledIntervalProcessor implements Runnable - { - private ScheduledFuture mScheduledFuture; - private boolean mStopped = false; - - /** - * Commands this processor to do a shutdown at the end of this or the next iteration. Once successfully - * shutdown, it will invoke the performDisposal() method to cleanup this instance. - */ - public void stop() - { - mStopped = true; - } - - /** - * Starts buffer processing on a fixed interval using a scheduled thread pool - */ - public void start() - { - if(mScheduledFuture == null) - { - mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this, 0, - BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS, TimeUnit.MILLISECONDS); - } - } - - /** - * Implementation of the Runnable interface to periodically send a heartbeat and then process buffer samples. - */ - @Override - public void run() - { - try - { - if(!mStopped) - { - try - { - getHeartbeatManager().broadcast(); - } - catch(Throwable t) - { - mLog.error("Error while sending heartbeat", t); - } - } - - if(!mStopped) - { - try - { - processSamples(); - } - catch(Throwable t) - { - mLog.error("Error while processing samples", t); - } - } - - if(mStopped) - { - if(mScheduledFuture != null) - { - //Set may-interrupt to false so that we can complete this iteration - mScheduledFuture.cancel(false); - } - - mScheduledFuture = null; - - try - { - getHeartbeatManager().broadcast(); - performDisposal(); - } - catch(Throwable t) - { - mLog.error("Error during final shutdown processing of samples", t); - } - } - } - catch(Throwable t) - { - mLog.error("Error during heartbeat processing", t); - } - } - } +// /** +// * Processor to periodically invoke buffer sample processing on an interval timer. At each interval this processor +// * sends a heartbeat to the registered consumer and then commands the sub-class implementation to process any +// * queued buffers and distribute complex buffer sample(s) to the registered consumer. +// */ +// public class ScheduledIntervalProcessor implements Runnable +// { +// private ScheduledFuture mScheduledFuture; +// private boolean mStopped = false; +// +// /** +// * Commands this processor to do a shutdown at the end of this or the next iteration. Once successfully +// * shutdown, it will invoke the performDisposal() method to cleanup this instance. +// */ +// public void stop() +// { +// mStopped = true; +// } +// +// /** +// * Starts buffer processing on a fixed interval using a scheduled thread pool +// */ +// public void start() +// { +// if(mScheduledFuture == null) +// { +// mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this, 0, +// BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS, TimeUnit.MILLISECONDS); +// } +// } +// +// /** +// * Implementation of the Runnable interface to periodically send a heartbeat and then process buffer samples. +// */ +// @Override +// public void run() +// { +// try +// { +// if(!mStopped) +// { +// try +// { +// getHeartbeatManager().broadcast(); +// } +// catch(Throwable t) +// { +// mLog.error("Error while sending heartbeat", t); +// } +// } +// +// if(!mStopped) +// { +// try +// { +// processSamples(); +// } +// catch(Throwable t) +// { +// mLog.error("Error while processing samples", t); +// } +// } +// +// if(mStopped) +// { +// if(mScheduledFuture != null) +// { +// //Set may-interrupt to false so that we can complete this iteration +// mScheduledFuture.cancel(false); +// } +// +// mScheduledFuture = null; +// +// try +// { +// getHeartbeatManager().broadcast(); +// performDisposal(); +// } +// catch(Throwable t) +// { +// mLog.error("Error during final shutdown processing of samples", t); +// } +// } +// } +// catch(Throwable t) +// { +// mLog.error("Error during heartbeat processing", t); +// } +// } +// } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java index 9b469276..2a1612e3 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java @@ -1,6 +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 @@ -14,13 +14,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.fcd; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.adapter.ComplexShortAdapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.mixer.ComplexMixer; import io.github.dsheirer.source.tuner.MixerTunerType; @@ -90,11 +90,11 @@ public abstract class FCDTunerController extends TunerController } mComplexMixer = new ComplexMixer( mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(), - new ComplexShortAdapter(tunerType.getDisplayString())); + new ComplexShortAdapter()); mComplexMixer.setBufferSize(tunerType.getAudioFormat().getFrameSize() * getBufferSampleCount()); - mComplexMixer.setBufferListener(mReusableBufferBroadcaster); + mComplexMixer.setBufferListener(mNativeBufferBroadcaster); } @Override @@ -107,7 +107,7 @@ public abstract class FCDTunerController extends TunerController * Overrides the super class functionality to auto-start the complex mixer and provide samples to listeners */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { boolean hasExistingListeners = hasBufferListeners(); @@ -124,7 +124,7 @@ public abstract class FCDTunerController extends TunerController * more registered listeners */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java index 43cd895c..644550f5 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java @@ -1,32 +1,28 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.hackrf; +import io.github.dsheirer.buffer.SignedByteNativeBufferFactory; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.source.tuner.usb.USBTunerController; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; -import io.github.dsheirer.source.tuner.usb.converter.SignedByteSampleConverter; import org.apache.commons.io.EndianUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +57,7 @@ public class HackRFTunerController extends USBTunerController public static final double USABLE_BANDWIDTH_PERCENT = 0.90; public static final int DC_SPIKE_AVOID_BUFFER = 5000; - private NativeBufferConverter mNativeBufferConverter = new SignedByteSampleConverter(); + private SignedByteNativeBufferFactory mNativeBufferFactory = new SignedByteNativeBufferFactory(); private USBTransferProcessor mUSBTransferProcessor; private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE_5_0; @@ -131,7 +127,7 @@ public class HackRFTunerController extends USBTunerController name = "HackRF - Unidentified Serial"; } - mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mNativeBufferConverter, + mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE, this); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java index e41983d8..070c58fb 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java @@ -1,34 +1,31 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.manager; +import io.github.dsheirer.buffer.NativeSampleDelayBuffer; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexDelayBuffer; 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.CICTunerChannelSource; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; +import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import org.slf4j.Logger; @@ -48,11 +45,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager private final static int DELAY_BUFFER_DURATION_MILLISECONDS = 2000; - private List mChannelSources = new CopyOnWriteArrayList<>(); +// private List mChannelSources = new CopyOnWriteArrayList<>(); + private List mChannelSources = new CopyOnWriteArrayList<>(); private SortedSet mTunerChannels = new TreeSet<>(); private TunerController mTunerController; private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor(); - private ReusableComplexDelayBuffer mSampleDelayBuffer; + private NativeSampleDelayBuffer mSampleDelayBuffer; public HeterodyneChannelSourceManager(TunerController tunerController) { @@ -80,8 +78,11 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager try { //Attempt to create the channel source first, in case we get a filter design exception - CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor, - tunerChannel, mTunerController.getSampleRate(), channelSpecification); +// CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor, +// tunerChannel, mTunerController.getSampleRate(), channelSpecification); + + HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor, + tunerChannel, mTunerController.getSampleRate(), channelSpecification); //Add to the list of channel sources so that it will receive the tuner frequency change mChannelSources.add(tunerChannelSource); @@ -181,7 +182,8 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager */ private void broadcastToChannels(SourceEvent sourceEvent) { - for(CICTunerChannelSource channelSource : mChannelSources) +// for(CICTunerChannelSource channelSource : mChannelSources) + for(HalfBandTunerChannelSource channelSource : mChannelSources) { try { @@ -201,7 +203,8 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager */ private void updateTunerFrequency(long tunerFrequency) { - for(CICTunerChannelSource channelSource : mChannelSources) +// for(CICTunerChannelSource channelSource : mChannelSources) + for(HalfBandTunerChannelSource channelSource : mChannelSources) { channelSource.setFrequency(tunerFrequency); } @@ -216,19 +219,13 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager if(mSampleDelayBuffer == null) { int delayBufferSize = (int)(DELAY_BUFFER_DURATION_MILLISECONDS / mTunerController.getBufferDuration()); - mSampleDelayBuffer = new ReusableComplexDelayBuffer(delayBufferSize, mTunerController.getBufferDuration()); - - mLog.debug("Created/registered complex sample delay buffer of size [" + delayBufferSize + - "] buffers and delay duration [" + DELAY_BUFFER_DURATION_MILLISECONDS + - "ms] for tuner controller [" + mTunerController.getClass().getName() + - "] with buffer duration [" + mTunerController.getBufferDuration() + "]"); - + mSampleDelayBuffer = new NativeSampleDelayBuffer(delayBufferSize, mTunerController.getBufferDuration()); mTunerController.addBufferListener(mSampleDelayBuffer); } } /** - * Deregisters the complex sample delay buffer and disposes of any queued reusable buffers. + * De-registers the complex sample delay buffer and disposes of any queued reusable buffers. */ private void stopDelayBuffer() { @@ -251,28 +248,32 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager switch(sourceEvent.getEvent()) { case REQUEST_START_SAMPLE_STREAM: - if(sourceEvent.getSource() instanceof CICTunerChannelSource) + if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource) { startDelayBuffer(); //The start sample stream request contains a start timestamp and the delay buffer //will preload the channel with delayed sample buffers that either contain the //timestamp or occur later/newer than the timestamp. - mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(), - sourceEvent.getValue().longValue()); +// mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(), +// sourceEvent.getValue().longValue()); + mSampleDelayBuffer.addListener((HalfBandTunerChannelSource)sourceEvent.getSource(), + sourceEvent.getValue().longValue()); } break; case REQUEST_STOP_SAMPLE_STREAM: - if(sourceEvent.getSource() instanceof CICTunerChannelSource) + if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource) { - mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource()); +// mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource()); + mSampleDelayBuffer.removeListener((HalfBandTunerChannelSource)sourceEvent.getSource()); stopDelayBuffer(); } break; case REQUEST_SOURCE_DISPOSE: - if(sourceEvent.getSource() instanceof CICTunerChannelSource) + if(sourceEvent.getSource() instanceof HalfBandTunerChannelSource) { - CICTunerChannelSource channelSource = (CICTunerChannelSource)sourceEvent.getSource(); +// CICTunerChannelSource channelSource = (CICTunerChannelSource)sourceEvent.getSource(); + HalfBandTunerChannelSource channelSource = (HalfBandTunerChannelSource) sourceEvent.getSource(); mChannelSources.remove(channelSource); mTunerChannels.remove(channelSource.getTunerChannel()); channelSource.dispose(); diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java index 151f88ca..d769a02e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.manager; @@ -405,6 +402,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager * Allocates a tuner channel source for the tuner channel. * * @param tunerChannel for requested source + * @param channelSpecification for the requested channel * @return allocated DDC tuner channel source, or null if the channel cannot be provided by this source manager */ @Override @@ -434,7 +432,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager } //If we're successful to here, allocate the channel - return mPolyphaseChannelManager.getChannel(tunerChannel, channelSpecification); + return mPolyphaseChannelManager.getChannel(tunerChannel); } catch(SourceException se) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java index 3528a4a4..77a846ba 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/recording/RecordingTunerController.java @@ -1,28 +1,25 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.recording; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; @@ -73,15 +70,7 @@ public class RecordingTunerController extends TunerController } mComplexWaveSource = new ComplexWaveSource(new File(recordingPath), true); - mComplexWaveSource.setListener(new Listener() - { - @Override - public void receive(ReusableComplexBuffer reusableComplexBuffer) - { - //Send to parent class to broadcast to listeners - broadcast(reusableComplexBuffer); - } - }); + mComplexWaveSource.setListener(complexSamples -> broadcast(complexSamples)); try { @@ -135,7 +124,7 @@ public class RecordingTunerController extends TunerController } @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { super.addBufferListener(listener); @@ -150,13 +139,13 @@ public class RecordingTunerController extends TunerController } @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); - if(!mReusableBufferBroadcaster.hasListeners() && mComplexWaveSource != null) + if(!mNativeBufferBroadcaster.hasListeners() && mComplexWaveSource != null) { - mComplexWaveSource.setListener((Listener)null); + mComplexWaveSource.setListener((Listener)null); mComplexWaveSource.stop(); mRunning = false; } diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java index a2bc3ef5..b39d1d95 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java @@ -1,36 +1,32 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.rtl; +import io.github.dsheirer.buffer.ByteNativeBufferFactory; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.TunerManager; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.source.tuner.usb.USBTunerController; -import io.github.dsheirer.source.tuner.usb.converter.ByteSampleConverter; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.usb4java.Device; @@ -79,7 +75,7 @@ public abstract class RTL2832TunerController extends USBTunerController private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE; - protected NativeBufferConverter mNativeBufferConverter = new ByteSampleConverter(); + protected ByteNativeBufferFactory mNativeBufferFactory = new ByteNativeBufferFactory(); protected int mOscillatorFrequency = 28800000; //28.8 MHz protected USBTransferProcessor mUSBTransferProcessor; protected Descriptor mDescriptor; @@ -162,7 +158,7 @@ public abstract class RTL2832TunerController extends USBTunerController String deviceName = getTunerType().getLabel() + " " + getUniqueID(); - mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferConverter, + mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE, this); } @@ -1526,7 +1522,7 @@ public abstract class RTL2832TunerController extends USBTunerController * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started. */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { boolean hasExistingListeners = hasBufferListeners(); @@ -1542,7 +1538,7 @@ public abstract class RTL2832TunerController extends USBTunerController * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners. */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); @@ -1564,15 +1560,15 @@ public abstract class RTL2832TunerController extends USBTunerController * * @param deviceName to use when logging information or errors * @param deviceHandle to the USB bulk transfer device - * @param nativeBufferConverter specific to the tuner's byte buffer format for converting to floating point I/Q samples + * @param nativeBufferFactory specific to the tuner's byte buffer format for converting to floating point I/Q samples * @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144. * @param tunerErrorListener to receive error state message */ public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, - NativeBufferConverter nativeBufferConverter, int bufferSize, + ByteNativeBufferFactory nativeBufferFactory, int bufferSize, ITunerErrorListener tunerErrorListener) { - super(deviceName, deviceHandle, nativeBufferConverter, bufferSize, tunerErrorListener); + super(deviceName, deviceHandle, nativeBufferFactory, bufferSize, tunerErrorListener); } @Override diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java index bc1ad6f7..305ed705 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.rtl.e4k; @@ -256,7 +253,7 @@ public class E4KTunerController extends RTL2832TunerController String deviceName = getTunerType().getLabel() + " " + getUniqueID(); - mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferConverter, + mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory, USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE, this); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java index 0e5cdab6..8914c7fa 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java @@ -1,26 +1,29 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.test; -import io.github.dsheirer.dsp.mixer.IOscillator; -import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.OscillatorFactory; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; import io.github.dsheirer.util.ThreadPool; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; @@ -33,12 +36,11 @@ public class SampleGenerator { private final static Logger mLog = LoggerFactory.getLogger(SampleGenerator.class); - private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster(); - private IOscillator mOscillator; + private Broadcaster mNativeBufferBroadcaster = new Broadcaster<>(); + private IComplexOscillator mComplexOscillator; private int mSweepUpdateInterval; private long mInterval; - private int mSamplesPerInterval; - private boolean mReuseBuffers; + private int mSamplesPerInterval = 65536; private ScheduledFuture mScheduledFuture; /** @@ -48,22 +50,20 @@ public class SampleGenerator * * @param sampleRate of the complex samples * @param frequency of the tone produced by the generator - * @param interval in milliseconds for generating samples * @param sweepUpdateRate to turn on sweeping of the tone frequency where the frequency is incremented by the * rate specified at each interval, resetting to 1 Hz after reaching/exceeding the highest frequency. */ - public SampleGenerator(int sampleRate, long frequency, long interval, int sweepUpdateRate) + public SampleGenerator(int sampleRate, long frequency, int sweepUpdateRate) { if(FastMath.abs(sweepUpdateRate) >= sampleRate) { throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate"); } - mOscillator = new LowPhaseNoiseOscillator(frequency, sampleRate); - mInterval = interval; - mSweepUpdateInterval = sweepUpdateRate; + mComplexOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate); - updateSamplesPerInterval(); + mInterval = 1000 / (sampleRate / mSamplesPerInterval); + mSweepUpdateInterval = sweepUpdateRate; } /** @@ -73,19 +73,10 @@ public class SampleGenerator * * @param sampleRate of the complex samples * @param frequency of the tone produced by the generator - * @param interval in milliseconds for generating samples */ - public SampleGenerator(int sampleRate, long frequency, long interval) + public SampleGenerator(int sampleRate, long frequency) { - this(sampleRate, frequency, interval, 0); - } - - /** - * Updates the number of complex samples generated per interval - */ - private void updateSamplesPerInterval() - { - mSamplesPerInterval = (int)((double)mOscillator.getSampleRate() * ((double)mInterval / 1000.0)); + this(sampleRate, frequency, 0); } /** @@ -125,11 +116,11 @@ public class SampleGenerator * * @param listener to receive complex sample buffers */ - public void addListener(Listener listener) + public void addListener(Listener listener) { - mComplexBufferBroadcaster.addListener(listener); + mNativeBufferBroadcaster.addListener(listener); - if(mComplexBufferBroadcaster.getListenerCount() == 1) + if(mNativeBufferBroadcaster.getListenerCount() == 1) { start(); } @@ -138,11 +129,11 @@ public class SampleGenerator /** * Removes the listener and stops the sample generator if there are no more listeners. */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { - mComplexBufferBroadcaster.removeListener(listener); + mNativeBufferBroadcaster.removeListener(listener); - if(mComplexBufferBroadcaster.getListenerCount() == 0) + if(mNativeBufferBroadcaster.getListenerCount() == 0) { stop(); } @@ -154,7 +145,7 @@ public class SampleGenerator */ public void setFrequency(long frequency) { - mOscillator.setFrequency(frequency); + mComplexOscillator.setFrequency(frequency); } /** @@ -162,7 +153,7 @@ public class SampleGenerator */ public long getFrequency() { - return (long)mOscillator.getFrequency(); + return (long) mComplexOscillator.getFrequency(); } /** @@ -171,13 +162,13 @@ public class SampleGenerator */ public void setSampleRate(int sampleRate) { - mOscillator.setSampleRate(sampleRate); - updateSamplesPerInterval(); + mComplexOscillator.setSampleRate(sampleRate); + mInterval = 1000 / (sampleRate / mSamplesPerInterval); } public double getSampleRate() { - return mOscillator.getSampleRate(); + return mComplexOscillator.getSampleRate(); } /** @@ -186,44 +177,52 @@ public class SampleGenerator public class Generator implements Runnable { private int mTriggerInterval = 0; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("SampleGenerator"); @Override public void run() { - if(mComplexBufferBroadcaster.hasListeners()) + try { - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(mSamplesPerInterval); - - mOscillator.generateComplex(reusableComplexBuffer); - - mComplexBufferBroadcaster.broadcast(reusableComplexBuffer); - - if(mSweepUpdateInterval != 0) + if(mNativeBufferBroadcaster.hasListeners()) { - mTriggerInterval++; + float[] samples = mComplexOscillator.generate(mSamplesPerInterval); - if(mTriggerInterval >= 10) + long now = System.currentTimeMillis(); + + FloatNativeBuffer buffer = new FloatNativeBuffer(samples, now); + + mNativeBufferBroadcaster.broadcast(buffer); + + if(mSweepUpdateInterval != 0) { - mTriggerInterval = 0; + mTriggerInterval++; - long updatedFrequency = (long)mOscillator.getFrequency() + mSweepUpdateInterval; + if(mTriggerInterval >= 10) + { + mTriggerInterval = 0; - if(updatedFrequency > mOscillator.getSampleRate() / 2) - { - mOscillator.setFrequency(mOscillator.getSampleRate() / -2); - } - else if(updatedFrequency < mOscillator.getSampleRate() / -2) - { - mOscillator.setFrequency(mOscillator.getSampleRate() / 2); - } - else - { - mOscillator.setFrequency(updatedFrequency); + long updatedFrequency = (long) mComplexOscillator.getFrequency() + mSweepUpdateInterval; + + if(updatedFrequency > mComplexOscillator.getSampleRate() / 2) + { + mComplexOscillator.setFrequency(mComplexOscillator.getSampleRate() / -2); + } + else if(updatedFrequency < mComplexOscillator.getSampleRate() / -2) + { + mComplexOscillator.setFrequency(mComplexOscillator.getSampleRate() / 2); + } + else + { + mComplexOscillator.setFrequency(updatedFrequency); + } } } } } + catch(Throwable t) + { + t.printStackTrace(); + } } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java index 00876662..a3cd4da4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java @@ -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,12 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.test; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; @@ -32,7 +32,7 @@ public class TestTunerController extends TunerController public static final long MINIMUM_FREQUENCY = 1l; public static final long MAXIMUM_FREQUENCY = 1000000000l; - public static final int SAMPLE_RATE = 10000000; + public static final int SAMPLE_RATE = 2_400_000; public static final int DC_NOISE_BANDWIDTH = 0; public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00; @@ -50,9 +50,9 @@ public class TestTunerController extends TunerController super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH_PERCENTAGE); int sweepRate = 0; //Hz per interval - long initialToneFrequency = SAMPLE_RATE / 2 + 100; - mSampleGenerator = new SampleGenerator(SAMPLE_RATE, initialToneFrequency, SAMPLE_GENERATION_INTERVAL, - sweepRate); +// long initialToneFrequency = SAMPLE_RATE / 2 + 100; + long initialToneFrequency = 50000; + mSampleGenerator = new SampleGenerator(SAMPLE_RATE, initialToneFrequency, sweepRate); try { @@ -78,13 +78,13 @@ public class TestTunerController extends TunerController } @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { mSampleGenerator.addListener(listener); } @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mSampleGenerator.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java index 42ac28e5..067e15d4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java @@ -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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.usb; @@ -86,18 +83,8 @@ public class USBMasterProcessor { mProcessor.reset(); mProcessorThread = new Thread(mProcessor); - mProcessorThread.setName("USB Event Processor"); - mProcessorThread.setDaemon(true); - - try - { - mProcessorThread.setPriority(Thread.MAX_PRIORITY); - } - catch(IllegalArgumentException iae) - { - //Do nothing ... couldn't set the priority - } - + mProcessorThread.setName("sdrtrunk USB event processor"); + mProcessorThread.setPriority(Thread.MAX_PRIORITY); mProcessorThread.start(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java index 54d5d7be..972ab226 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java @@ -1,31 +1,28 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.tuner.usb; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.buffer.INativeBufferFactory; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.TunerManager; -import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,13 +56,13 @@ public class USBTransferProcessor implements TransferCallback private List mTransfersToDispose = new ArrayList<>(); private List mTransfersToSubmit = new ArrayList<>(); - //Tuner format-specific byte buffer to IQ float sample converter - private NativeBufferConverter mNativeBufferConverter; + //Tuner specific native buffer factory for processing/conversion of raw samples + private INativeBufferFactory mNativeBufferFactory; //Byte array transfer buffers size in bytes private int mBufferSize; - private Listener mComplexBufferListener; + private Listener mNativeBufferListener; //Handle to the USB bulk transfer device private DeviceHandle mUsbBulkTransferDeviceHandle; @@ -84,16 +81,16 @@ public class USBTransferProcessor implements TransferCallback * * @param deviceName to use when logging information or errors * @param usbBulkTransferDeviceHandle to the USB bulk transfer device - * @param nativeBufferConverter specific to the tuner's byte buffer format for converting to floating point I/Q samples + * @param nativeBufferFactory specific to the tuner's byte buffer format for converting to floating point I/Q samples * @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144. */ public USBTransferProcessor(String deviceName, DeviceHandle usbBulkTransferDeviceHandle, - NativeBufferConverter nativeBufferConverter, int bufferSize, + INativeBufferFactory nativeBufferFactory, int bufferSize, ITunerErrorListener tunerErrorListener) { mDeviceName = deviceName; mUsbBulkTransferDeviceHandle = usbBulkTransferDeviceHandle; - mNativeBufferConverter = nativeBufferConverter; + mNativeBufferFactory = nativeBufferFactory; mBufferSize = bufferSize; mITunerErrorListener = tunerErrorListener; } @@ -358,11 +355,11 @@ public class USBTransferProcessor implements TransferCallback /** * Sets the listener and auto-starts the buffer processor */ - public void setListener(Listener listener) + public void setListener(Listener listener) { - if(mComplexBufferListener == null || !mComplexBufferListener.equals(listener)) + if(mNativeBufferListener == null || !mNativeBufferListener.equals(listener)) { - mComplexBufferListener = listener; + mNativeBufferListener = listener; boolean success = start(); @@ -378,10 +375,10 @@ public class USBTransferProcessor implements TransferCallback */ public void removeListener() { - if(mComplexBufferListener != null) + if(mNativeBufferListener != null) { stop(); - mComplexBufferListener = null; + mNativeBufferListener = null; } } @@ -555,14 +552,13 @@ public class USBTransferProcessor implements TransferCallback { if(mRunning.get()) { - ByteBuffer nativeBuffer = transfer.buffer(); - - ReusableComplexBuffer reusableComplexBuffer = - mNativeBufferConverter.convert(nativeBuffer, transfer.actualLength()); - - if(mComplexBufferListener != null) + if(mNativeBufferListener != null) { - mComplexBufferListener.receive(reusableComplexBuffer); + //Pass the transfer's byte buffer so the native buffer factory can make a copy of the + //byte array contents and package it as a native buffer. + INativeBuffer nativeBuffer = mNativeBufferFactory.getBuffer(transfer.buffer(), + System.currentTimeMillis()); + mNativeBufferListener.receive(nativeBuffer); } } @@ -577,7 +573,7 @@ public class USBTransferProcessor implements TransferCallback } else { - ThreadPool.SCHEDULED.submit(() -> restart()); + ThreadPool.CACHED.submit(() -> restart()); transfer = null; } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java index 185f901a..ccf2e8af 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java @@ -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,12 +14,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.usb; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.tuner.TunerController; public abstract class USBTunerController extends TunerController @@ -44,7 +44,7 @@ public abstract class USBTunerController extends TunerController * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started. */ @Override - public void addBufferListener(Listener listener) + public void addBufferListener(Listener listener) { boolean hasExistingListeners = hasBufferListeners(); @@ -60,7 +60,7 @@ public abstract class USBTunerController extends TunerController * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners. */ @Override - public void removeBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { super.removeBufferListener(listener); diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java index eed4e47f..61c8b565 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java @@ -1,30 +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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.usb.converter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import java.nio.ByteBuffer; import java.nio.FloatBuffer; public abstract class NativeBufferConverter { - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("NativeBufferConverter"); - /** * Converts native byte buffers into complex float samples and produces reusable complex sample buffers. Tracks * each reusable buffer until the downstream consumer(s) are finished with the buffer and then reuses the buffer. @@ -34,23 +34,20 @@ public abstract class NativeBufferConverter } /** - * Converts the native byte buffer to complex float samples and returns the samples in a reusable buffer. Internally - * tracks each reusable buffer until all consumers indicate they are finished processing the buffer and then reuses - * the buffer. + * Converts the native byte buffer to complex float samples. * * @param byteBuffer containing native memory byte samples - * @return native buffer samples converted to complex floats loaded into a reusable buffer + * @return native buffer samples converted to interleaved complex floats */ - public ReusableComplexBuffer convert(ByteBuffer byteBuffer, int length) + public InterleavedComplexSamples convert(ByteBuffer byteBuffer, int length) { FloatBuffer floatBuffer = convertSamples(byteBuffer, length); - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(floatBuffer.capacity()); + float[] samples = new float[floatBuffer.capacity()]; + floatBuffer.rewind(); + floatBuffer.get(samples); - //The reusable buffer will rewind the float buffer to the beginning - reusableComplexBuffer.reloadFrom(floatBuffer, System.currentTimeMillis()); - - return reusableComplexBuffer; + return new InterleavedComplexSamples(samples, System.currentTimeMillis()); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java new file mode 100644 index 00000000..567d61d4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeSampleBuffer.java @@ -0,0 +1,51 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.source.tuner.usb.converter; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.util.List; + +/** + * Base class for native samples byte buffer. This class holds a copy of the byte array of samples + * that was copied from a native byte buffer into the JVM heap space. + * + * Consumers of this sample buffer must make a copy of the contents in either interleaved or non-interleaved + * sample array buffers. + */ +public abstract class NativeSampleBuffer +{ + private byte[] mSamples; + + public NativeSampleBuffer(byte[] samples) + { + mSamples = samples; + } + + protected byte[] getSamples() + { + return mSamples; + } + + public abstract List convert(); + + public abstract List convertInterleaved(); +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java index 90c94006..5096aa46 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/SignedByteSampleConverter.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.tuner.usb.converter; import java.nio.ByteBuffer; @@ -77,4 +80,10 @@ public class SignedByteSampleConverter extends NativeBufferConverter return mFloatBuffer; } + + public static void main(String[] args) + { + SignedByteSampleConverter converter = new SignedByteSampleConverter(); + int a = 0; + } } diff --git a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java index 0cefd028..e089d192 100644 --- a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java +++ b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java @@ -1,33 +1,31 @@ /* + * ***************************************************************************** + * 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 - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.source.wave; +import io.github.dsheirer.buffer.FloatNativeBuffer; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.source.ComplexSource; +import io.github.dsheirer.sample.SampleType; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; +import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.util.ThreadPool; import org.apache.commons.math3.util.FastMath; @@ -44,7 +42,7 @@ import java.util.Arrays; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -public class ComplexWaveSource extends ComplexSource implements IControllableFileSource, AutoCloseable +public class ComplexWaveSource extends Source implements IControllableFileSource, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger(ComplexWaveSource.class); @@ -52,10 +50,9 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil private int mBytesPerFrame; private int mFrameCounter = 0; private long mFrequency = 0; - private Listener mListener; + private Listener mListener; private AudioInputStream mInputStream; private File mFile; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ComplexWaveSource"); private boolean mAutoReplay; private ScheduledFuture mReplayController; @@ -85,6 +82,11 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil this(file, false); } + @Override public SampleType getSampleType() + { + return SampleType.COMPLEX; + } + @Override public void setSourceEventListener(Listener listener) { @@ -167,7 +169,6 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil @Override public long getFrameCount() throws IOException { - // TODO Auto-generated method stub return 0; } @@ -275,11 +276,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil } float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer); - - ReusableComplexBuffer reusableBuffer = mReusableComplexBufferQueue.getBuffer(samples.length); - System.arraycopy(samples, 0, reusableBuffer.getSamples(), 0, samples.length); - reusableBuffer.setTimestamp(System.currentTimeMillis()); - mListener.receive(reusableBuffer); + mListener.receive(new FloatNativeBuffer(samples, System.currentTimeMillis())); } } } @@ -288,8 +285,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil * Registers the listener to receive sample buffers as they are read from * the wave file */ - @Override - public void setListener(Listener listener) + public void setListener(Listener listener) { mListener = listener; } @@ -297,7 +293,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil /** * Unregisters the listener from receiving sample buffers */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mListener = null; } diff --git a/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java index 3c1f0213..89983f49 100644 --- a/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java +++ b/src/main/java/io/github/dsheirer/source/wave/RealWaveSource.java @@ -1,29 +1,29 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.source.wave; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; +import io.github.dsheirer.sample.SampleType; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; -import io.github.dsheirer.source.RealSource; +import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,16 +36,14 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; -public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable +public class RealWaveSource extends Source implements IControllableFileSource, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger(RealWaveSource.class); - - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealWaveSource"); private IFrameLocationListener mFrameLocationListener; private int mBytesPerFrame; private int mFrameCounter = 0; private long mFrequency = 0; - private Listener mListener; + private Listener mListener; private AudioInputStream mInputStream; private File mFile; @@ -54,6 +52,11 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc mFile = file; } + @Override public SampleType getSampleType() + { + return SampleType.REAL; + } + @Override public void setSourceEventListener(Listener listener) { @@ -229,11 +232,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc } float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer); - - ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(samples.length); - reusableFloatBuffer.reloadFrom(samples, System.currentTimeMillis()); - - mListener.receive(reusableFloatBuffer); + mListener.receive(samples); } } } @@ -241,8 +240,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc /** * Registers the listener to receive sample buffers as they are read from the wave file */ - @Override - public void setListener(Listener listener) + public void setListener(Listener listener) { mListener = listener; } @@ -250,8 +248,7 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc /** * Unregisters the listener from receiving sample buffers */ - @Override - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mListener = null; } diff --git a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java b/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java deleted file mode 100644 index c8b8de57..00000000 --- a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java +++ /dev/null @@ -1,518 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * 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 - * ***************************************************************************** - */ -package io.github.dsheirer.spectrum; - -import io.github.dsheirer.controller.channel.Channel; -import io.github.dsheirer.controller.channel.ChannelEvent; -import io.github.dsheirer.controller.channel.ChannelEventListener; -import io.github.dsheirer.controller.channel.ChannelProcessingManager; -import io.github.dsheirer.dsp.filter.Filters; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter; -import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType; -import io.github.dsheirer.module.ProcessingChain; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; -import io.github.dsheirer.settings.ColorSetting.ColorSettingName; -import io.github.dsheirer.settings.ColorSettingMenuItem; -import io.github.dsheirer.settings.Setting; -import io.github.dsheirer.settings.SettingChangeListener; -import io.github.dsheirer.settings.SettingsManager; -import io.github.dsheirer.source.SourceEvent; -import io.github.dsheirer.spectrum.converter.DFTResultsConverter; -import io.github.dsheirer.spectrum.converter.RealDecibelConverter; -import io.github.dsheirer.spectrum.menu.AveragingItem; -import io.github.dsheirer.spectrum.menu.DFTSizeItem; -import io.github.dsheirer.spectrum.menu.FFTWindowTypeItem; -import io.github.dsheirer.spectrum.menu.FrameRateItem; -import io.github.dsheirer.spectrum.menu.SmoothingItem; -import io.github.dsheirer.spectrum.menu.SmoothingTypeItem; -import net.miginfocom.swing.MigLayout; - -import javax.swing.JLayeredPane; -import javax.swing.JMenu; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JSeparator; -import javax.swing.SwingUtilities; -import java.awt.Component; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.util.concurrent.atomic.AtomicBoolean; - -public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener, - Listener, SettingChangeListener, SpectralDisplayAdjuster -{ - private static final long serialVersionUID = 1L; - - private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.REAL); - private DFTResultsConverter mDFTConverter = new RealDecibelConverter(); - private JLayeredPane mLayeredPane; - private SpectrumPanel mSpectrumPanel; - private ChannelOverlayPanel mOverlayPanel; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ChannelSpectrumPanel"); - - private Channel mCurrentChannel; - - private int mSampleBufferSize = 2400; - - private HalfBandFilter mDecimatingFilter = new HalfBandFilter( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true); - - private AtomicBoolean mEnabled = new AtomicBoolean(); - - private SettingsManager mSettingsManager; - private ChannelProcessingManager mChannelProcessingManager; - - public ChannelSpectrumPanel(SettingsManager settingsManager, - ChannelProcessingManager channelProcessingManager) - { - mSettingsManager = settingsManager; - mChannelProcessingManager = channelProcessingManager; - - if(mSettingsManager != null) - { - mSettingsManager.addListener(this); - } - - mSpectrumPanel = new SpectrumPanel(mSettingsManager); - mSpectrumPanel.setAveraging(1); - - mOverlayPanel = new ChannelOverlayPanel(mSettingsManager); - mDFTProcessor.addConverter(mDFTConverter); - mDFTConverter.addListener(mSpectrumPanel); - - /* Set the DFTProcessor to the decimated 24kHz sample rate */ - mDFTProcessor.process(SourceEvent.sampleRateChange(24000.0)); - - initGui(); - } - - public void dispose() - { - setEnabled(false); - - mDFTProcessor.dispose(); - - if(mSettingsManager != null) - { - mSettingsManager.removeListener(this); - } - - mSettingsManager = null; - mCurrentChannel = null; - mDFTProcessor = null; - mSpectrumPanel = null; - } - - public void setFrameRate(int framesPerSecond) - { - mSampleBufferSize = (int)(48000 / framesPerSecond); - - mDFTProcessor.setFrameRate(framesPerSecond); - } - - private void initGui() - { - setLayout(new MigLayout("insets 0 0 0 0 ", - "[grow,fill]", - "[grow,fill]")); - - mLayeredPane = new JLayeredPane(); - mLayeredPane.addComponentListener(new ResizeListener()); - - MouseEventProcessor mouser = new MouseEventProcessor(); - - mOverlayPanel.addMouseListener(mouser); - mOverlayPanel.addMouseMotionListener(mouser); - - mLayeredPane.add(mSpectrumPanel, 0, 0); - mLayeredPane.add(mOverlayPanel, 1, 0); - - add(mLayeredPane); - } - - public void setEnabled(boolean enabled) - { - if(enabled && mEnabled.compareAndSet(false, true)) - { - start(); - } - else if(!enabled && mEnabled.compareAndSet(true, false)) - { - stop(); - } - } - - @Override - public void channelChanged(ChannelEvent event) - { - switch(event.getEvent()) - { - case NOTIFICATION_SELECTION_CHANGE: - //ChannelSelectionManager ensures that only 1 channel can be - //selected and any previously selected channel will be first - //deselected before we get a new selection event - if(event.getChannel().isSelected()) - { - if(mCurrentChannel != null) - { - stop(); - mCurrentChannel = null; - } - - mCurrentChannel = event.getChannel(); - - if(mEnabled.get()) - { - start(); - } - } - else - { - stop(); - mCurrentChannel = null; - } - break; - case NOTIFICATION_PROCESSING_STOP: - if(event.getChannel() == mCurrentChannel) - { - if(mEnabled.get()) - { - stop(); - } - - mCurrentChannel = null; - } - break; - } - } - - private void start() - { - if(mEnabled.get() && mCurrentChannel != null && mCurrentChannel.isProcessing()) - { - ProcessingChain processingChain = mChannelProcessingManager - .getProcessingChain(mCurrentChannel); - - if(processingChain != null) - { - processingChain.addDemodulatedAudioListener(this); - mDFTProcessor.start(); - } - } - } - - private void stop() - { - if(mCurrentChannel != null && mCurrentChannel.isProcessing()) - { - ProcessingChain processingChain = mChannelProcessingManager.getProcessingChain(mCurrentChannel); - - if(processingChain != null) - { - processingChain.removeDemodulatedAudioListener(this); - } - } - - mDFTProcessor.stop(); - - mSpectrumPanel.clearSpectrum(); - } - - @Override - public void settingChanged(Setting setting) - { - if(mSpectrumPanel != null) - { - mSpectrumPanel.settingChanged(setting); - } - if(mOverlayPanel != null) - { - mOverlayPanel.settingChanged(setting); - } - } - - @Override - public void settingDeleted(Setting setting) - { - if(mSpectrumPanel != null) - { - mSpectrumPanel.settingDeleted(setting); - } - - if(mOverlayPanel != null) - { - mOverlayPanel.settingDeleted(setting); - } - } - - @Override - public void receive(ReusableFloatBuffer buffer) - { - ReusableFloatBuffer decimated = mDecimatingFilter.filter(buffer); - - //Hack: we're placing real samples in a complex buffer that the DFT - //processor is expecting. - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(decimated.getSamples().length); - reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis()); - mDFTProcessor.receive(reusableComplexBuffer); - } - - /** - * Monitors the sizing of the layered pane and resizes the spectrum and - * channel panels whenever the layered pane is resized - */ - public class ResizeListener implements ComponentListener - { - @Override - public void componentResized(ComponentEvent e) - { - Component c = e.getComponent(); - - mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight()); - mOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight()); - } - - @Override - public void componentHidden(ComponentEvent arg0) - { - } - - @Override - public void componentMoved(ComponentEvent arg0) - { - } - - @Override - public void componentShown(ComponentEvent arg0) - { - } - } - - /** - * Mouse event handler for the channel panel. - */ - public class MouseEventProcessor implements MouseMotionListener, MouseListener - { - @Override - public void mouseMoved(MouseEvent event) - { - update(event); - } - - @Override - public void mouseDragged(MouseEvent event) - { - update(event); - } - - private void update(MouseEvent event) - { - if(event.getComponent() == mOverlayPanel) - { - mOverlayPanel.setCursorLocation(event.getPoint()); - } - } - - @Override - public void mouseEntered(MouseEvent e) - { - if(e.getComponent() == mOverlayPanel) - { - mOverlayPanel.setCursorVisible(true); - } - } - - @Override - public void mouseExited(MouseEvent e) - { - mOverlayPanel.setCursorVisible(false); - } - - /** - * Displays the context menu. - */ - @Override - public void mouseClicked(MouseEvent event) - { - if(SwingUtilities.isRightMouseButton(event)) - { - JPopupMenu contextMenu = new JPopupMenu(); - - /** - * Color Menus - */ - JMenu colorMenu = new JMenu("Color"); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_CURSOR)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_LINE)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_BACKGROUND)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_GRADIENT_BOTTOM)); - - colorMenu.add(new ColorSettingMenuItem(mSettingsManager, - ColorSettingName.SPECTRUM_GRADIENT_TOP)); - - contextMenu.add(colorMenu); - - /** - * Display items: fft and frame rate - */ - JMenu displayMenu = new JMenu("Display"); - contextMenu.add(displayMenu); - - /** - * Averaging menu - */ - JMenu averagingMenu = new JMenu("Averaging"); - averagingMenu.add( - new AveragingItem(ChannelSpectrumPanel.this, 2)); - displayMenu.add(averagingMenu); - - /** - * FFT width - */ - JMenu fftWidthMenu = new JMenu("FFT Width"); - displayMenu.add(fftWidthMenu); - - for(DFTSize width : DFTSize.values()) - { - fftWidthMenu.add(new DFTSizeItem(mDFTProcessor, width)); - } - - /** - * DFT Processor Frame Rate - */ - JMenu frameRateMenu = new JMenu("Frame Rate"); - displayMenu.add(frameRateMenu); - - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 14)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 16)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 18)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 20)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 25)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 30)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 40)); - frameRateMenu.add(new FrameRateItem(mDFTProcessor, 50)); - - /** - * FFT Window Type - */ - JMenu fftWindowType = new JMenu("Window Type"); - displayMenu.add(fftWindowType); - - for(WindowType type : WindowType.values()) - { - fftWindowType.add( - new FFTWindowTypeItem(mDFTProcessor, type)); - } - - /** - * Smoothing menu - */ - JMenu smoothingMenu = new JMenu("Smoothing"); - - if(mSpectrumPanel.getSmoothingType() != SmoothingType.NONE) - { - smoothingMenu.add(new SmoothingItem(ChannelSpectrumPanel.this, 5)); - smoothingMenu.add(new JSeparator()); - } - - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.GAUSSIAN)); - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.TRIANGLE)); - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.RECTANGLE)); - smoothingMenu.add(new SmoothingTypeItem(ChannelSpectrumPanel.this, SmoothingType.NONE)); - - displayMenu.add(smoothingMenu); - - if(contextMenu != null) - { - contextMenu.show(mOverlayPanel, - event.getX(), - event.getY()); - } - } - } - - @Override - public void mousePressed(MouseEvent e) - { - } - - @Override - public void mouseReleased(MouseEvent e) - { - } - } - - @Override - public int getAveraging() - { - return mSpectrumPanel.getAveraging(); - } - - @Override - public void setAveraging(int averaging) - { - mSpectrumPanel.setAveraging(averaging); - } - - public void setSampleSize(double sampleSize) - { - mSpectrumPanel.setSampleSize(sampleSize); - } - - @Override - public int getSmoothing() - { - return mSpectrumPanel.getSmoothing(); - } - - @Override - public void setSmoothing(int smoothing) - { - mSpectrumPanel.setSmoothing(smoothing); - } - - @Override - public SmoothingType getSmoothingType() - { - return mSpectrumPanel.getSmoothingType(); - } - - @Override - public void setSmoothingType(SmoothingType type) - { - mSpectrumPanel.setSmoothingType(type); - } -} diff --git a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java b/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java similarity index 52% rename from src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java rename to src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java index c1b5bb95..092fa521 100644 --- a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/ComplexDftProcessor.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,28 +14,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.spectrum; -import io.github.dsheirer.dsp.filter.Window; -import io.github.dsheirer.dsp.filter.Window.WindowType; +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.controller.NamingThreadFactory; +import io.github.dsheirer.dsp.window.WindowFactory; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.properties.SystemProperties; -import io.github.dsheirer.sample.IOverflowListener; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.source.ISourceEventProcessor; -import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.spectrum.converter.DFTResultsConverter; -import io.github.dsheirer.util.ThreadPool; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -45,37 +41,29 @@ import java.util.concurrent.atomic.AtomicBoolean; * Processes both complex samples or float samples and dispatches a float array of DFT results, using configurable fft * size and output dispatch timelines. */ -public class DFTProcessor implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor +public class ComplexDftProcessor implements Listener, IDFTWidthChangeProcessor { - private static final Logger mLog = LoggerFactory.getLogger(DFTProcessor.class); - private static final int BUFFER_QUEUE_MAX_SIZE = 20; - private static final int BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD = 6; + private static final Logger mLog = LoggerFactory.getLogger(ComplexDftProcessor.class); private static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate"; //The Cosine and Hann windows seem to offer the best spectral display with minimal bin leakage/smearing private WindowType mWindowType = WindowType.HANN; - private double[] mWindow; + private float[] mWindow; private DFTSize mDFTSize = DFTSize.FFT04096; private DFTSize mNewDFTSize = DFTSize.FFT04096; private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize()); private int mFrameRate; - private int mSampleRate = 2400000; //Initial high value until we receive update from tuner - private int mFrameSize; - private int mFrameFlushCount; - private int mFrameOverlapCount; - private SampleType mSampleType; private AtomicBoolean mRunning = new AtomicBoolean(); private ScheduledFuture mProcessorTaskHandle; - private CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList(); - private OverflowableBufferStream mOverflowableBufferStream = new OverflowableBufferStream(BUFFER_QUEUE_MAX_SIZE, - BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD, mDFTSize.getSize()); - private float[] mPreviousSamples; + private ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk dft processor")); + private CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); + private NativeBufferManager mDftBufferManager = new NativeBufferManager(mDFTSize.getSize() * 2); + private float[] mPreviousSamples = new float[mDFTSize.getSize() * 2]; - public DFTProcessor(SampleType sampleType) + public ComplexDftProcessor() { - setSampleType(sampleType); mFrameRate = SystemProperties.getInstance().get(FRAME_RATE_PROPERTY, 20); - calculateConsumptionRate(); + setWindowType(mWindowType); start(); } @@ -84,19 +72,9 @@ public class DFTProcessor implements Listener, ISourceEve stop(); mListeners.clear(); - mOverflowableBufferStream.clear(); mWindow = null; } - /** - * Sets the listener to receive buffer overflow/reset indications - * @param listener - */ - public void setOverflowListener(IOverflowListener listener) - { - mOverflowableBufferStream.setOverflowListener(listener); - } - public WindowType getWindowType() { return mWindowType; @@ -105,30 +83,12 @@ public class DFTProcessor implements Listener, ISourceEve public void setWindowType(WindowType windowType) { mWindowType = windowType; - - if(mSampleType == SampleType.COMPLEX) - { - mWindow = Window.getWindow(mWindowType, mDFTSize.getSize() * 2); - } - else - { - mWindow = Window.getWindow(mWindowType, mDFTSize.getSize()); - } + updateWindow(); } - /** - * Sets the processor mode to Float or Complex, depending on the sample - * types that will be delivered for processing - */ - public void setSampleType(SampleType type) + private void updateWindow() { - mSampleType = type; - setWindowType(mWindowType); - } - - public SampleType getSampleType() - { - return mSampleType; + mWindow = WindowFactory.getWindow(mWindowType, mDFTSize.getSize() * 2); } /** @@ -159,11 +119,7 @@ public class DFTProcessor implements Listener, ISourceEve } mFrameRate = framesPerSecond; - SystemProperties.getInstance().set(FRAME_RATE_PROPERTY, mFrameRate); - - calculateConsumptionRate(); - restart(); } @@ -175,7 +131,7 @@ public class DFTProcessor implements Listener, ISourceEve int initialDelay = 0; int period = (int) (1000 / mFrameRate); - mProcessorTaskHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, + mProcessorTaskHandle = mExecutorService.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, TimeUnit.MILLISECONDS); } } @@ -205,9 +161,9 @@ public class DFTProcessor implements Listener, ISourceEve * Places the sample into a transfer queue for future processing. */ @Override - public void receive(ReusableComplexBuffer sampleBuffer) + public void receive(T buffer) { - mOverflowableBufferStream.offer(sampleBuffer); + mDftBufferManager.add(buffer); } private void calculate() @@ -219,31 +175,15 @@ public class DFTProcessor implements Listener, ISourceEve try { - if(mFrameFlushCount > 0) - { - mOverflowableBufferStream.flush(mFrameFlushCount); - } - //If this throws an IO exception, the buffer queue is (temporarily) empty and we return from the method - float[] samples = mOverflowableBufferStream.get(mFrameSize, mFrameOverlapCount); - - Window.apply(mWindow, samples); - - if(mSampleType == SampleType.REAL) - { - mFFT.realForward(samples); - } - else - { - mFFT.complexForward(samples); - } - + float[] samples = mDftBufferManager.get(mDFTSize.getSize()); + WindowFactory.apply(mWindow, samples); + mFFT.complexForward(samples); mPreviousSamples = samples; } catch(IOException ioe) { - //No new data, dispatch the previous samples again - //no-op + //Not enough samples available, dispatch the previous samples again } catch(Exception e) { @@ -264,8 +204,8 @@ public class DFTProcessor implements Listener, ISourceEve */ private void dispatch(float[] results) { - - for (DFTResultsConverter mListener : mListeners) { + for (DFTResultsConverter mListener : mListeners) + { mListener.receive(results); } } @@ -286,9 +226,7 @@ public class DFTProcessor implements Listener, ISourceEve if(mRunning.compareAndSet(false, true)) { checkFFTSize(); - calculate(); - mRunning.set(false); } } @@ -309,98 +247,13 @@ public class DFTProcessor implements Listener, ISourceEve if(mNewDFTSize.getSize() != mDFTSize.getSize()) { mDFTSize = mNewDFTSize; - - calculateConsumptionRate(); - - setWindowType(mWindowType); - + updateWindow(); mFFT = new FloatFFT_1D(mDFTSize.getSize()); } } public void clearBuffer() { - mOverflowableBufferStream.clear(); - } - - @Override - public void process(SourceEvent event) - { - switch(event.getEvent()) - { - case NOTIFICATION_SAMPLE_RATE_CHANGE: - mSampleRate = event.getValue().intValue(); - calculateConsumptionRate(); - break; - default: - break; - } - } - - /** - * Calculates the frame size, flush count and overlap count to use for each calculation cycle. - */ - private void calculateConsumptionRate() - { - int floatsPerSample = mSampleType == SampleType.COMPLEX ? 2 : 1; - - mFrameSize = mDFTSize.getSize() * floatsPerSample; - mPreviousSamples = new float[mFrameSize]; - - int productionRate = mSampleRate * floatsPerSample; - int consumptionRate = mFrameRate * mFrameSize; - int residual = productionRate - consumptionRate; - - if(residual < 0) - { - mFrameFlushCount = 0; - mFrameOverlapCount = -residual / mFrameRate; - - if(mFrameOverlapCount * mFrameRate < -residual) - { - mFrameOverlapCount ++; - } - } - else if(residual > 0) - { - mFrameOverlapCount = 0; - mFrameFlushCount = residual / mFrameRate; - - if(mFrameFlushCount * mFrameRate < residual) - { - mFrameFlushCount++; - } - } - - //Ensure we're flushing or overlapping by even multiples of the sample size - if(mFrameOverlapCount % floatsPerSample != 0) - { - mFrameOverlapCount++; - } - - if(mFrameFlushCount % floatsPerSample != 0) - { - mFrameFlushCount++; - } - - //If the overlap size is greater than the frame size, we can't do that. Automatically decrease the frame rate - //until we reach a legitimate overlap value. - if(mFrameOverlapCount >= mFrameSize) - { - mLog.warn("Unable to provide frame rate [" + mFrameRate + "] for current DFT size [" + mDFTSize.getSize() + - "] - reducing frame rate"); - - mFrameRate--; - - if(mFrameRate <= 0) - { - //Consider reducing the FFT size as a possible alternative corrective measure - throw new IllegalStateException("Unable to determine a viable frame rate based on incoming sample " + - "rate, DFT size and DFT frame rate"); - } - - //Recursively call this method to test the new frame rate - calculateConsumptionRate(); - } + mDftBufferManager.clear(); } } diff --git a/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java b/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java index 76714e66..5efa52e5 100644 --- a/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/IDFTWidthChangeProcessor.java @@ -1,7 +1,26 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + package io.github.dsheirer.spectrum; public interface IDFTWidthChangeProcessor { - public void setDFTSize( DFTSize size ); - public DFTSize getDFTSize(); + void setDFTSize( DFTSize size ); + DFTSize getDFTSize(); } diff --git a/src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java b/src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java new file mode 100644 index 00000000..840ed5a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/spectrum/NativeBufferManager.java @@ -0,0 +1,150 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.spectrum; + +import io.github.dsheirer.buffer.INativeBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.LinkedTransferQueue; + +/** + * Thread-safe native buffer manager. Receives incoming native buffers in a non-blocking manner. Received buffers are + * only enqueued if they are needed to meet the next requested sample array size. Once the incoming producer quota + * is reached, incoming buffers are ignored. + * + * Transferred native buffers are enqueued on the consumer side until enough samples are amassed to satisfy a get + * operation. + * + * The queuing on both the producer and consumer side ensures the thread-safe transfer queue never overfills. + * + * The estimated request size argument sets the initial quota of samples to enqueue on the producer size. This value + * is updated on the consumer side with each request. This class is designed for the get() operation to consistently + * request the same size buffer, with occasional changes. + * + * @param native buffer + */ +public class NativeBufferManager +{ + private LinkedTransferQueue mTransferQueue = new LinkedTransferQueue<>(); + private List mProducerQueue = new ArrayList<>(); + private List mConsumerQueue = new ArrayList<>(); + private int mRequestSize; + private int mProducerAvailable; + + /** + * Constructs an instance + * @param estimatedRequestSize initially, for each complex sample retrieval. + */ + public NativeBufferManager(int estimatedRequestSize) + { + mRequestSize = estimatedRequestSize; + } + /** + * Adds the buffer to this manager if it's needed. + * @param nativeBuffer to add + */ + public void add(T nativeBuffer) + { + //If we have enough produced buffers and the transfer queue is empty ... move them to the queue + if(mProducerAvailable >= mRequestSize && mTransferQueue.isEmpty()) + { + mTransferQueue.addAll(mProducerQueue); + mProducerQueue.clear(); + mProducerAvailable = 0; + } + + //Add this buffer to the producer queue if it's needed to meet the anticipated request size + if(mProducerAvailable < mRequestSize) + { + mProducerQueue.add(nativeBuffer); + mProducerAvailable += nativeBuffer.sampleCount(); + } + } + + /** + * Clears/removes all queued native buffers + */ + public void clear() + { + mTransferQueue.clear(); + mProducerQueue.clear(); + mConsumerQueue.clear(); + } + + /** + * Returns an array of complex samples of the length requested. If there are not sufficient samples in the queue + * to fully satisfy the request, an IOException is thrown. + * @param requestedSamples number of complex sample pairs. + * @return a float array with twice as many (2 samples for each I & Q pair) values as requested. + * @throws IOException if there are (temporarily) insufficient samples available. + */ + public float[] get(int requestedSamples) throws IOException + { + //Update the producer's sample quota if it changes + if(requestedSamples != mRequestSize) + { + mRequestSize = requestedSamples; + } + + List drained = new ArrayList<>(); + mTransferQueue.drainTo(drained); + mConsumerQueue.addAll(drained); + + int count = 0; + + for(T buffer: mConsumerQueue) + { + count += buffer.sampleCount(); + } + + if(count < requestedSamples) + { + throw new IOException("Insufficient samples. Please try again later"); + } + + float[] samples = new float[requestedSamples * 2]; + int samplesPointer = 0; + + for(T buffer: mConsumerQueue) + { + Iterator iterator = buffer.iteratorInterleaved(); + + while(iterator.hasNext() && samplesPointer < samples.length) + { + InterleavedComplexSamples complexSamples = iterator.next(); + int toCopy = Math.min(samples.length - samplesPointer, complexSamples.samples().length); + System.arraycopy(complexSamples.samples(), 0, samples, samplesPointer, toCopy); + samplesPointer += toCopy; + } + + if(samplesPointer >= samples.length) + { + break; + } + } + + mConsumerQueue.clear(); + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java index 46c90006..ddb1dca8 100644 --- a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java +++ b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java @@ -1,39 +1,45 @@ -/******************************************************************************* - * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.spectrum; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.sample.OverflowableTransferQueue; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.complex.InterleavedComplexSamples; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Iterator; import java.util.LinkedList; -public class OverflowableBufferStream extends OverflowableTransferQueue +public class OverflowableBufferStream extends OverflowableTransferQueue { private static final Logger mLog = LoggerFactory.getLogger(OverflowableBufferStream.class); private int mFlushCount = 0; - private ReusableComplexBuffer mCurrentBuffer; + private Iterator mCurrentNativeBufferIterator; + private InterleavedComplexSamples mCurrentBuffer; private int mCurrentBufferPointer = 0; private FloatBuffer mFloatBuffer; - private LinkedList mBufferList = new LinkedList<>(); + private LinkedList mBufferList = new LinkedList<>(); private int mBufferFetchLimit; /** @@ -90,7 +96,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue 0) { - mFloatBuffer.put(mCurrentBuffer.getSamples(), mCurrentBufferPointer, toCopy); + mFloatBuffer.put(mCurrentBuffer.samples(), mCurrentBufferPointer, toCopy); mCurrentBufferPointer += toCopy; } } @@ -150,7 +156,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue 0) { - int available = mCurrentBuffer.getSamples().length - mCurrentBufferPointer; + int available = mCurrentBuffer.samples().length - mCurrentBufferPointer; if(available <= mFlushCount) { @@ -181,35 +187,41 @@ public class OverflowableBufferStream extends OverflowableTransferQueue - * * ***************************************************************************** + * 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 + * **************************************************************************** */ package io.github.dsheirer.spectrum; import com.jidesoft.swing.JideSplitPane; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelProcessingManager; -import io.github.dsheirer.dsp.filter.Window.WindowType; import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType; +import io.github.dsheirer.dsp.window.WindowType; import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.settings.ColorSetting.ColorSettingName; import io.github.dsheirer.settings.ColorSettingMenuItem; import io.github.dsheirer.settings.SettingsManager; @@ -83,8 +79,8 @@ import java.awt.event.MouseWheelEvent; import java.util.ArrayList; import java.util.Hashtable; -public class SpectralDisplayPanel extends JPanel implements Listener, ISourceEventProcessor, - IDFTWidthChangeProcessor +public class SpectralDisplayPanel extends JPanel + implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor { private static final long serialVersionUID = 1L; @@ -104,7 +100,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener * Mouse scrolling and zooming are supported and the waterfall display can * be paused. - * + *

* Complex sample buffers are processed by a DFTProcessor and the output of * the DFT is translated to decibels for display in the spectrum and * waterfall components. @@ -175,8 +171,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener * 0 No Zoom * 1 2x Zoom * 2 4x Zoom @@ -239,8 +233,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel))); + viewChannel.addActionListener( + e -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel))); channelMenu.add(viewChannel); } @@ -659,29 +635,21 @@ public class SpectralDisplayPanel extends JPanel implements Listener labels = new Hashtable<>(); + Hashtable labels = new Hashtable<>(); labels.put(0, new JLabel("1x")); labels.put(1, new JLabel("2x")); labels.put(2, new JLabel("4x")); @@ -893,8 +849,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener + * **************************************************************************** + */ + package io.github.dsheirer.spectrum.menu; -import io.github.dsheirer.dsp.filter.Window.WindowType; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.spectrum.ComplexDftProcessor; -import javax.swing.*; +import javax.swing.JCheckBoxMenuItem; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -11,29 +30,28 @@ public class FFTWindowTypeItem extends JCheckBoxMenuItem { private static final long serialVersionUID = 1L; - private DFTProcessor mDFTProcessor; + private ComplexDftProcessor mComplexDftProcessor; private WindowType mWindowType; - - public FFTWindowTypeItem( DFTProcessor processor, WindowType windowType ) - { - super( windowType.toString() ); - - mDFTProcessor = processor; - mWindowType = windowType; - if( processor.getWindowType() == mWindowType ) - { - setSelected( true ); - } - - addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) + public FFTWindowTypeItem(ComplexDftProcessor processor, WindowType windowType) + { + super(windowType.toString()); + + mComplexDftProcessor = processor; + mWindowType = windowType; + + if(processor.getWindowType() == mWindowType) + { + setSelected(true); + } + + addActionListener(new ActionListener() + { + @Override public void actionPerformed(ActionEvent arg0) { - mDFTProcessor.setWindowType( mWindowType ); + mComplexDftProcessor.setWindowType(mWindowType); } - } ); + }); } } diff --git a/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java b/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java index cb6c6cf3..3e0cc53b 100644 --- a/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java +++ b/src/main/java/io/github/dsheirer/spectrum/menu/FrameRateItem.java @@ -1,6 +1,25 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + package io.github.dsheirer.spectrum.menu; -import io.github.dsheirer.spectrum.DFTProcessor; +import io.github.dsheirer.spectrum.ComplexDftProcessor; import javax.swing.*; import java.awt.event.ActionEvent; @@ -10,14 +29,14 @@ public class FrameRateItem extends JCheckBoxMenuItem { private static final long serialVersionUID = 1L; - private DFTProcessor mDFTProcessor; + private ComplexDftProcessor mComplexDftProcessor; private int mFrameRate; - public FrameRateItem( DFTProcessor processor, int frameRate ) + public FrameRateItem(ComplexDftProcessor processor, int frameRate ) { super( String.valueOf( frameRate ) ); - mDFTProcessor = processor; + mComplexDftProcessor = processor; mFrameRate = frameRate; if( processor.getFrameRate() == mFrameRate ) @@ -30,7 +49,7 @@ public class FrameRateItem extends JCheckBoxMenuItem @Override public void actionPerformed( ActionEvent arg0 ) { - mDFTProcessor.setFrameRate( mFrameRate ); + mComplexDftProcessor.setFrameRate( mFrameRate ); } } ); } diff --git a/src/main/java/io/github/dsheirer/util/Dispatcher.java b/src/main/java/io/github/dsheirer/util/Dispatcher.java new file mode 100644 index 00000000..b614727a --- /dev/null +++ b/src/main/java/io/github/dsheirer/util/Dispatcher.java @@ -0,0 +1,200 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ +package io.github.dsheirer.util; + +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Threaded processor for receiving elements from a separate producer thread and forwarding those buffers to a + * registered listener on this consumer/dispatcher thread. + */ +public class Dispatcher implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(Dispatcher.class); + private static final long OVERFLOW_LOG_EVENT_WAIT_PERIOD = TimeUnit.SECONDS.toMillis(10); + private LinkedBlockingQueue mQueue; + private Listener mListener; + private AtomicBoolean mRunning = new AtomicBoolean(); + private String mThreadName; + private Thread mThread; + private E mPoisonPill; + private long mLastOverflowLogEvent; + + /** + * Constructs an instance + * @param maxSize of the internal queue + * @param threadName to name the dispatcher thread + * @param poisonPill of type E, used to kill the thread + */ + public Dispatcher(int maxSize, String threadName, E poisonPill) + { + if(poisonPill == null) + { + throw new IllegalArgumentException("Poison pill must be non-null"); + } + mQueue = new LinkedBlockingQueue<>(maxSize); + mThreadName = threadName; + mPoisonPill = poisonPill; + } + + /** + * Listener to receive the queued buffers each time this processor runs. + */ + protected Listener getListener() + { + return mListener; + } + + /** + * Sets or changes the listener to receive buffers from this processor. + * @param listener to receive buffers + */ + public void setListener(Listener 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 + */ + public void receive(E e) + { + if(mRunning.get()) + { + if(!mQueue.offer(e)) + { + if(System.currentTimeMillis() > (mLastOverflowLogEvent + OVERFLOW_LOG_EVENT_WAIT_PERIOD)) + { + mLastOverflowLogEvent = System.currentTimeMillis(); + mLog.warn("Temporary buffer overflow for thread [" + mThreadName + "] - throwing away samples"); + } + } + } + else + { + mLog.error("Dispatcher [" + mThreadName + "] received an element when not in a running state - element:" + e.getClass()); + } + } + + /** + * Starts this buffer processor and allows queuing of incoming buffers. + */ + public void start() + { + if(mRunning.compareAndSet(false, true)) + { + mThread = new Thread(new Processor()); + mThread.setName(mThreadName); + mThread.setPriority(Thread.MAX_PRIORITY); + mThread.start(); + } + } + + /** + * Stops this buffer processor and waits up to two seconds for the processing thread to terminate. + */ + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + mQueue.offer(mPoisonPill); + + try + { + mThread.join(2000); + mThread = null; + } + catch(Exception e) + { + mLog.error("Timeout while waiting to join terminating buffer processor thread"); + } + } + } + + /** + * Finishes processing any queued buffers and then stops the processing thread. + */ + public void flushAndStop() + { + mQueue.offer(mPoisonPill); + } + + /** + * 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() + { + mQueue.clear(); + + E element; + + while(mRunning.get()) + { + try + { + element = mQueue.take(); + + if(mPoisonPill.equals(element)) + { + mRunning.set(false); + } + else if(element != null) + { + if(mListener == null) + { + throw new IllegalStateException("Listener for [" + mThreadName + "] is null"); + } + mListener.receive(element); + } + } + catch(InterruptedException e) + { + mLog.error("Buffer processor thread was interrupted"); + } + catch(Exception e) + { + mLog.error("Error while processing element", e); + } + } + + mQueue.clear(); + } + } +} diff --git a/src/main/java/io/github/dsheirer/util/ThreadPool.java b/src/main/java/io/github/dsheirer/util/ThreadPool.java index 3458263e..18d66c15 100644 --- a/src/main/java/io/github/dsheirer/util/ThreadPool.java +++ b/src/main/java/io/github/dsheirer/util/ThreadPool.java @@ -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,32 +14,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * **************************************************************************** + */ package io.github.dsheirer.util; import io.github.dsheirer.controller.NamingThreadFactory; -import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; public class ThreadPool { private final static Logger mLog = LoggerFactory.getLogger(ThreadPool.class); - - private static int CORES = Runtime.getRuntime().availableProcessors(); - public static ScheduledExecutorService SCHEDULED; - - static - { - //Create a scheduled thread pool sized according to the available processors/cores, minimum 2 - CORES = (FastMath.max(CORES, 2)); - - SCHEDULED = Executors.newScheduledThreadPool(CORES, new NamingThreadFactory("sdrtrunk")); - } + public static ScheduledExecutorService SCHEDULED = + Executors.newScheduledThreadPool(2, new NamingThreadFactory("sdrtrunk scheduled")); + public static ExecutorService CACHED = + Executors.newCachedThreadPool(new NamingThreadFactory("sdrtrunk cached")); /** * Application-wide shared thread pools and scheduled executor service. @@ -50,6 +43,6 @@ public class ThreadPool public static void logSettings() { - mLog.info("Application thread pool created with [" + CORES + "] threads"); + mLog.info("Application thread pool created SCHEDULED and CACHED executors threads"); } } diff --git a/src/main/java/io/github/dsheirer/vector/VectorUtilities.java b/src/main/java/io/github/dsheirer/vector/VectorUtilities.java new file mode 100644 index 00000000..05b459d5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/VectorUtilities.java @@ -0,0 +1,194 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector; + +import io.github.dsheirer.sample.complex.ComplexSamples; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utilities for working with Project Panama SIMD vectors in JDK 17+. + */ +public class VectorUtilities +{ + private static final Logger mLog = LoggerFactory.getLogger(VectorUtilities.class); + private static boolean mSpeciesMismatchLogged = false; + + /** + * Checks the species to determine if it is compatible with the preferred species for the runtime CPU + * and logs a warning if the species' lane width is wider than the preferred species ... which would be + * hugely inefficient. + * + * @param species to test + */ + public static void checkSpecies(VectorSpecies species) + { + if(FloatVector.SPECIES_PREFERRED.length() < species.length()) + { + if(!mSpeciesMismatchLogged) + { + mLog.warn("CPU supports maximum SIMD instructions of " + FloatVector.SPECIES_PREFERRED); + mSpeciesMismatchLogged = true; + } + } + } + + /** + * Checks the I/Q sample array length to be an integer multiple of the SIMD lane width. + * @param i samples + * @param q samples + * @param species used for SIMD operations + */ + public static void checkComplexArrayLength(float[] i, float[] q, VectorSpecies species) + { + if(i.length % species.length() != 0) + { + throw new IllegalArgumentException("I/Q buffer lengths [" + i.length + "] must be a power of 2 multiple of SIMD lane width [" + + species.length() + "]"); + } + } + + /** + * Checks the I/Q sample array length to be an integer multiple of the SIMD lane width. + * @param samples buffer + * @param species used for SIMD operations + */ + public static void checkComplexArrayLength(ComplexSamples samples, VectorSpecies species) + { + checkComplexArrayLength(samples.i(), samples.q(), species); + } + + /** + * Creates a vector mask for deinterleaving I samples from an interleaved complex sample vector. + * @param species of SIMD + * @return vector mask + */ + public static VectorMask getIVectorMask(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return VectorMask.fromArray(species, new boolean[]{true,false}, 0); + case 4: + return VectorMask.fromArray(species, new boolean[]{true,false,true,false}, 0); + case 8: + return VectorMask.fromArray(species, new boolean[]{true,false,true,false,true,false,true,false}, 0); + case 16: + return VectorMask.fromArray(species, new boolean[]{true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true,false}, 0); + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Creates a vector mask for deinterleaving Q samples from an interleaved complex sample vector. + * @param species of SIMD + * @return vector mask + */ + public static VectorMask getQVectorMask(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return VectorMask.fromArray(species, new boolean[]{false,true}, 0); + case 4: + return VectorMask.fromArray(species, new boolean[]{false,true,false,true}, 0); + case 8: + return VectorMask.fromArray(species, new boolean[]{false,true,false,true,false,true,false,true}, 0); + case 16: + return VectorMask.fromArray(species, new boolean[]{false,true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true}, 0); + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Creates an index map for routing inphase samples into a complex sample array + * @param species to determine lane width + * @return index map + */ + public static int[] getIIndexMap(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return new int[]{0,2}; + case 4: + return new int[]{0,2,4,6}; + case 8: + return new int[]{0,2,4,6,8,10,12,14}; + case 16: + return new int[]{0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30}; + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Creates an index map for routing quadrature samples into a complex sample array + * @param species to determine lane width + * @return index map + */ + public static int[] getQIndexMap(VectorSpecies species) + { + switch(species.length()) + { + case 2: + return new int[]{1,3}; + case 4: + return new int[]{1,3,5,7}; + case 8: + return new int[]{1,3,5,7,9,11,13,15}; + case 16: + return new int[]{1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31}; + default: + throw new IllegalArgumentException("Unrecognized vector species: " + species); + } + } + + /** + * Interleaves the samples from the I and Q vectors into a complex sample array + * @param iVector samples + * @param qVector samples + * @return interleaved sample array + */ + public static float[] interleave(FloatVector iVector, FloatVector qVector) + { + float[] interleaved = new float[iVector.length() * 2]; + float[] i = new float[iVector.length()]; + float[] q = new float[qVector.length()]; + + iVector.intoArray(i, 0); + qVector.intoArray(q, 0); + + for(int x = 0; x < i.length; x++) + { + interleaved[2 * x] = i[x]; + interleaved[2 * x + 1] = q[x]; + } + + return interleaved; + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java new file mode 100644 index 00000000..72a62b6e --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java @@ -0,0 +1,143 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Random; +import java.util.prefs.Preferences; + +/** + * Abstract calibration plugin base class + */ +public abstract class Calibration +{ + protected static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0"); + public static final Logger mLog = LoggerFactory.getLogger(Calibration.class); + private Preferences mPreferences = Preferences.userNodeForPackage(Calibration.class); + private CalibrationType mType; + private Implementation mImplementation; + + /** + * Constructs an instance + * @param type + */ + public Calibration(CalibrationType type) + { + mType = type; + } + + public Logger getLogger() + { + return mLog; + } + + /** + * Type of calibration plugin + */ + public CalibrationType getType() + { + return mType; + } + + /** + * Indicates if this plugin is calibrated. + */ + public boolean isCalibrated() + { + return getImplementation() != Implementation.UNCALIBRATED; + } + + /** + * Resets calibration status to uncalibrated for the plugin + */ + public void reset() + { + setImplementation(Implementation.UNCALIBRATED); + } + + /** + * Optimal implementation for this plugin + * @return implementation or uncalibrated if this plugin has not yet been calibrated + */ + public Implementation getImplementation() + { + if(mImplementation == null) + { + String implementation = mPreferences.get(getType().getPreferenceKey(), Implementation.UNCALIBRATED.name()); + mImplementation = Implementation.valueOf(implementation); + } + + return mImplementation; + } + + /** + * Sets the optimal implementation as determined via calibration. + * @param implementation to set + */ + protected void setImplementation(Implementation implementation) + { + mImplementation = implementation; + mPreferences.put(getType().getPreferenceKey(), implementation.name()); + } + + /** + * Executes calibration for the plugin + */ + public abstract void calibrate() throws CalibrationException; + + /** + * Generates an array of floating point samples in the range -1.0 - 1.0 + * @param size of array + * @return generated samples + */ + protected float[] getFloatSamples(int size) + { + Random random = new Random(); + + float[] samples = new float[size]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat() * 2.0f - 1.0f; + } + + return samples; + } + + /** + * Generates an array of floating point samples in the range -1.0 - 1.0 + * @param size of array + * @return generated samples + */ + protected short[] getShortSamples(int size) + { + Random random = new Random(); + + short[] samples = new short[size]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = (short)((random.nextFloat() * 2.0f - 1.0f) * Short.MAX_VALUE); + } + + return samples; + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java new file mode 100644 index 00000000..fc837dbb --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationException.java @@ -0,0 +1,17 @@ +package io.github.dsheirer.vector.calibrate; + +/** + * Error encountered while calibrating + */ +public class CalibrationException extends Exception +{ + /** + * Constructs a new instance + * @param description of the error + * @param error nested + */ + public CalibrationException(String description, Exception error) + { + super(description, error); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java new file mode 100644 index 00000000..e40faaf8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java @@ -0,0 +1,294 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate; + +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; +import io.github.dsheirer.vector.calibrate.airspy.AirspySampleConverterCalibration; +import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedCalibration; +import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedInterleavedCalibration; +import io.github.dsheirer.vector.calibrate.demodulator.FmDemodulatorCalibration; +import io.github.dsheirer.vector.calibrate.demodulator.SquelchingFmDemodulatorCalibration; +import io.github.dsheirer.vector.calibrate.filter.FirFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealDcRemovalCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand11TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand15TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand23TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBand63TapFilterCalibration; +import io.github.dsheirer.vector.calibrate.filter.RealHalfBandDefaultFilterCalibration; +import io.github.dsheirer.vector.calibrate.gain.ComplexGainCalibration; +import io.github.dsheirer.vector.calibrate.gain.ComplexGainControlCalibration; +import io.github.dsheirer.vector.calibrate.mixer.ComplexMixerCalibration; +import io.github.dsheirer.vector.calibrate.oscillator.ComplexOscillatorCalibration; +import io.github.dsheirer.vector.calibrate.oscillator.RealOscillatorCalibration; +import io.github.dsheirer.vector.calibrate.window.WindowCalibration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Determines the optimal (scalar vs vector) class to use for the current CPU architecture. + */ +public class CalibrationManager +{ + private static final Logger mLog = LoggerFactory.getLogger(CalibrationManager.class); + private Map mCalibrationMap = new HashMap<>(); + private static CalibrationManager sInstance; + private static VectorCalibrationPreference sVectorCalibrationPreference; + + /** + * Uses the singleton pattern to construct a single instance. + */ + private CalibrationManager() + { + } + + /** + * Access a singleton instance of this class, initializing the instance with the + * specified User Preferences instance, if it hasn't already been intitialized. + * + * Note: invoke this method first with a preferences instance to ensure it is the one that is used. + */ + public static CalibrationManager getInstance(UserPreferences userPreferences) + { + if(sVectorCalibrationPreference == null) + { + sVectorCalibrationPreference = userPreferences.getVectorCalibrationPreference(); + } + + return getInstance(); + } + + /** + * Access a singleton instance of this class. + */ + public static CalibrationManager getInstance() + { + if(sInstance == null) + { + if(sVectorCalibrationPreference == null) + { + sVectorCalibrationPreference = new UserPreferences().getVectorCalibrationPreference(); + } + + sInstance = new CalibrationManager(); + + sInstance.add(new AirspySampleConverterCalibration()); + sInstance.add(new AirspyUnpackedCalibration()); + sInstance.add(new AirspyUnpackedInterleavedCalibration()); + sInstance.add(new ComplexGainCalibration()); + sInstance.add(new ComplexGainControlCalibration()); + sInstance.add(new ComplexOscillatorCalibration()); + sInstance.add(new ComplexMixerCalibration()); + sInstance.add(new FirFilterCalibration()); + sInstance.add(new FmDemodulatorCalibration()); + sInstance.add(new RealDcRemovalCalibration()); + sInstance.add(new RealHalfBand11TapFilterCalibration()); + sInstance.add(new RealHalfBand15TapFilterCalibration()); + sInstance.add(new RealHalfBand23TapFilterCalibration()); + sInstance.add(new RealHalfBand63TapFilterCalibration()); + sInstance.add(new RealHalfBandDefaultFilterCalibration()); + sInstance.add(new RealOscillatorCalibration()); + sInstance.add(new SquelchingFmDemodulatorCalibration()); + +// sInstance.add(new HilbertCalibration()); //Not currently used + sInstance.add(new WindowCalibration()); //Not currently used + } + + return sInstance; + } + + /** + * Adds the calibration to the map of calibrations for this manager. + * @param calibration to add + */ + private void add(Calibration calibration) + { + if(mCalibrationMap.containsKey(calibration.getType())) + { + throw new IllegalStateException("Calibration Type [" + calibration.getType() + + "] is already registered for [" + mCalibrationMap.get(calibration.getType()).getClass()); + } + + mCalibrationMap.put(calibration.getType(), calibration); + } + + /** + * List of calibration types available + */ + public List getCalibrationTypes() + { + return List.copyOf(mCalibrationMap.keySet()); + } + + /** + * Access a calibration by type + * @param type of calibration + * @return calibration instance, or null. + */ + public Calibration getCalibration(CalibrationType type) + { + return mCalibrationMap.get(type); + } + + /** + * Identifies the optimal operation for the calibration type. + * @param type of calibration + * @return operation + */ + public Implementation getImplementation(CalibrationType type) + { + if(sVectorCalibrationPreference.isVectorEnabled()) + { + Calibration calibration = getCalibration(type); + + if(calibration != null) + { + return calibration.getImplementation(); + } + + return Implementation.UNCALIBRATED; + } + + return Implementation.SCALAR; + } + + /** + * Indicates if all calibrations are calibrated. + * @return true if all calibrations are calibrated or if there are no calibrations registered. + */ + public boolean isCalibrated() + { + for(Calibration calibration: mCalibrationMap.values()) + { + if(!calibration.isCalibrated()) + { + return false; + } + } + + return true; + } + + /** + * Resets all calibrations to an uncalibrated state. + */ + public void reset() + { + for(Calibration calibration: mCalibrationMap.values()) + { + calibration.reset(); + } + } + + /** + * Resets a specific calibration type. + */ + public void reset(CalibrationType type) + { + Calibration calibration = mCalibrationMap.get(type); + + if(calibration != null) + { + calibration.reset(); + } + } + + /** + * Calibrates any calibrations that are not currently calibrated + * @throws CalibrationException if any errors are encountered by any of the calibrations. + */ + public void calibrate() throws CalibrationException + { + List uncalibrated = getUncalibrated(); + + if(uncalibrated.isEmpty()) + { + mLog.info("No additional calibrations are required at this time."); + } + else + { + mLog.info("Calibrating software for optimal performance on this computer."); + mLog.info("*** Please be patient, this may take a few minutes ***"); + + int calibrationCounter = 0; + + for(Calibration calibration: uncalibrated) + { + if(!calibration.isCalibrated()) + { + mLog.info("Calibrating [" + ++calibrationCounter + " of " + uncalibrated.size() + + "] Type: " + calibration.getType()); + calibration.calibrate(); + } + } + + mLog.info("Calibration Complete!"); + } + } + + /** + * List of calibrations that need to be performed. + */ + public List getUncalibrated() + { + List uncalibrated = new ArrayList<>(); + + for(Calibration calibration: mCalibrationMap.values()) + { + if(!calibration.isCalibrated()) + { + uncalibrated.add(calibration); + } + } + + //Sort by calibration type + Collections.sort(uncalibrated, Comparator.comparing(Calibration::getType)); + + return uncalibrated; + } + + public static void main(String[] args) + { + CalibrationManager manager = getInstance(); +// manager.reset(); + manager.reset(CalibrationType.WINDOW); + + if(!manager.isCalibrated()) + { + try + { + manager.calibrate(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + System.out.println("Complete"); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java new file mode 100644 index 00000000..3a0d77c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java @@ -0,0 +1,92 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate; + +/** + * Calibrations used by the system + * + * Version numbers in each calibration can be incremented any time the calibration changes + * so that the calibration will automatically run. + */ +public enum CalibrationType +{ + //There's an oddity with these two vector implementations where it takes ~30x longer when it runs + //after any other calibrations ... so we always sort to top to run this one first + OSCILLATOR_COMPLEX("Complex Oscillator", 1), + GAIN_COMPLEX("Complex Gain", 1), + + AIRSPY_SAMPLE_CONVERTER("Airspy Sample Converter", 1), + AIRSPY_UNPACKED_INTERLEAVED_ITERATOR("Airspy Unpacked Interleaved Iterator", 1), + AIRSPY_UNPACKED_ITERATOR("Airspy Unpacked Iterator", 1), + DC_REMOVAL_REAL("Real DC Removal Filter", 1), + FILTER_FIR("FIR Filter", 1), + FILTER_HALF_BAND_REAL_11_TAP("Real Half-Band Decimation Filter - 11 Tap", 1), + FILTER_HALF_BAND_REAL_15_TAP("Real Half-Band Decimation Filter - 15 Tap", 1), + FILTER_HALF_BAND_REAL_23_TAP("Real Half-Band Decimation Filter - 23 Tap", 1), + FILTER_HALF_BAND_REAL_63_TAP("Real Half-Band Decimation Filter - 63 Tap", 1), + FILTER_HALF_BAND_REAL_DEFAULT("Real Half-Band Decimation Filter - Default", 1), + FM_DEMODULATOR("FM Demodulator", 1), + GAIN_CONTROL_COMPLEX("Complex Gain Control", 1), + HILBERT_TRANSFORM("Hilbert Transform", 1), + MIXER_COMPLEX("Complex Mixer", 1), + OSCILLATOR_REAL("Real Oscillator", 1), + SQUELCHING_FM_DEMODULATOR("Squelching FM Demodulator", 1), + WINDOW("Window", 1); + + private String mDescription; + private int mVersion; + + /** + * Constructs an instance + * @param description + * @param version of the calibration. + */ + CalibrationType(String description, int version) + { + mDescription = description; + mVersion = version; + } + + /** + * Formulates a unique value for the entry for use as a key value. + */ + public String getPreferenceKey() + { + return name() + " - V" + getVersion(); + } + + public String getDescription() + { + return mDescription; + } + + /** + * Version of the calibration + */ + public int getVersion() + { + return mVersion; + } + + @Override public String toString() + { + return getDescription(); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java b/src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java new file mode 100644 index 00000000..54d898cb --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/Implementation.java @@ -0,0 +1,15 @@ +package io.github.dsheirer.vector.calibrate; + +/** + * Identifies the optimal implementation (Scalar vs Vector) identified through calibration testing + */ +public enum Implementation +{ + SCALAR, + VECTOR_SIMD_PREFERRED, + VECTOR_SIMD_64, + VECTOR_SIMD_128, + VECTOR_SIMD_256, + VECTOR_SIMD_512, + UNCALIBRATED; +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java new file mode 100644 index 00000000..9dae658c --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspySampleConverterCalibration.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.airspy; + +import io.github.dsheirer.buffer.airspy.ScalarUnpackedSampleConverter; +import io.github.dsheirer.buffer.airspy.VectorUnpackedSampleConverter; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +import java.nio.ByteBuffer; + +public class AirspySampleConverterCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 262144; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARM_UP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + /** + * Constructs an instance + */ + public AirspySampleConverterCalibration() + { + super(CalibrationType.AIRSPY_SAMPLE_CONVERTER); + } + + @Override + public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + Mean vectorMean = new Mean(); + + byte[] samples = new byte[BUFFER_SIZE]; + ByteBuffer buffer = ByteBuffer.wrap(samples); + + for(int scalarTest = 0; scalarTest < WARM_UP_ITERATIONS; scalarTest++) + { + long score = testScalar(buffer); + scalarMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + for(int vectorTest = 0; vectorTest < WARM_UP_ITERATIONS; vectorTest++) + { + long score = testVector(buffer); + vectorMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + vectorMean.clear(); + + for(int scalarTest = 0; scalarTest < TEST_ITERATIONS; scalarTest++) + { + long score = testScalar(buffer); + scalarMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + for(int vectorTest = 0; vectorTest < TEST_ITERATIONS; vectorTest++) + { + long score = testVector(buffer); + vectorMean.increment(score); + } + + mLog.info("AIRSPY CONVERTER - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("AIRSPY CONVERTER - SETTING OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(ByteBuffer buffer) + { + long start = System.currentTimeMillis(); + + ScalarUnpackedSampleConverter scalor = new ScalarUnpackedSampleConverter(); + + long count = 0; + long accumulator = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + short[] converted = scalor.convert(buffer); + accumulator += converted[2]; + count++; + } + + return count + (accumulator * 0); + } + + private long testVector(ByteBuffer buffer) + { + VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter(); + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + short[] converted = vector.convert(buffer); + accumulator += converted[2]; + count++; + } + + return count + (accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java new file mode 100644 index 00000000..1be80f57 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedCalibration.java @@ -0,0 +1,309 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.airspy; + +import io.github.dsheirer.buffer.airspy.AirspyBufferIterator; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorScalar; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector128Bits; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector256Bits; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector512Bits; +import io.github.dsheirer.buffer.airspy.AirspyBufferIteratorVector64Bits; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calculates optimal implementation (SCALAR vs VECTOR) for non-interleaved airspy native buffers. + */ +public class AirspyUnpackedCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 131072; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + /** + * Constructs an instance + */ + public AirspyUnpackedCalibration() + { + super(CalibrationType.AIRSPY_UNPACKED_ITERATOR); + } + + @Override + public void calibrate() throws CalibrationException + { + short[] samples = getShortSamples(BUFFER_SIZE); + short[] residualI = getShortSamples(AirspyBufferIterator.I_OVERLAP); + short[] residualQ = getShortSamples(AirspyBufferIterator.Q_OVERLAP); + + //Warm-Up Phase .... + Mean scalarMean = new Mean(); + + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vector512Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector512(samples, residualI, residualQ); + vector512Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + case 8: + { + Mean vector256Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector256(samples, residualI, residualQ); + vector256Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + case 4: + { + Mean vector128Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector128(samples, residualI, residualQ); + vector128Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + case 2: + { + Mean vector64Mean = new Mean(); + for(int warmup = 0; warmup < WARMUP_ITERATIONS; warmup++) + { + long score = testVector64(samples, residualI, residualQ); + vector64Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + } + + //Test Phase .... + scalarMean.clear(); + + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + mLog.info("AIRSPY UNPACKED - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vector512Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector512(samples, residualI, residualQ); + vector512Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + case 8: + { + Mean vector256Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector256(samples, residualI, residualQ); + vector256Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + case 4: + { + Mean vector128Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector128(samples, residualI, residualQ); + vector128Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + case 2: + { + Mean vector64Mean = new Mean(); + for(int test = 0; test < TEST_ITERATIONS; test++) + { + long score = testVector64(samples, residualI, residualQ); + vector64Mean.increment(score); + } + + mLog.info("AIRSPY UNPACKED - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + } + + mLog.info("AIRSPY UNPACKED - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorScalar iterator = new AirspyBufferIteratorScalar(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector64(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector64Bits iterator = new AirspyBufferIteratorVector64Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector128(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector128Bits iterator = new AirspyBufferIteratorVector128Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector256(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector256Bits iterator = + new AirspyBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long testVector512(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyBufferIteratorVector512Bits iterator = + new AirspyBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().i()[2]; + count++; + } + } + + return count + (accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java new file mode 100644 index 00000000..aef91fa3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/airspy/AirspyUnpackedInterleavedCalibration.java @@ -0,0 +1,321 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.airspy; + +import io.github.dsheirer.buffer.airspy.AirspyBufferIterator; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorScalar; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector128Bits; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector256Bits; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector512Bits; +import io.github.dsheirer.buffer.airspy.AirspyInterleavedBufferIteratorVector64Bits; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calculates optimal implementation (SCALAR vs VECTOR) for interleaved airspy native buffers. + */ +public class AirspyUnpackedInterleavedCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 131072; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + /** + * Constructs an instance + */ + public AirspyUnpackedInterleavedCalibration() + { + super(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR); + } + + @Override + public void calibrate() throws CalibrationException + { + short[] samples = getShortSamples(BUFFER_SIZE); + short[] residualI = getShortSamples(AirspyBufferIterator.I_OVERLAP); + short[] residualQ = getShortSamples(AirspyBufferIterator.Q_OVERLAP); + + //Warm-Up Phase .... + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector512(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 8: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector256(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 4: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector128(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 2: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = calibrateVector64(samples, residualI, residualQ); + vectorMean.increment(score); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + + //Test Phase .... + scalarMean.clear(); + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateScalar(samples, residualI, residualQ); + scalarMean.increment(score); + } + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + mLog.info("AIRSPY UNPACKED INTERLEAVED - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + switch(FloatVector.SPECIES_PREFERRED.length()) + { + //Deliberate fall-through of each case statement so that we can test the largest + //SIMD lane width supported by hardware down to the smallest SIMD lane width. + case 16: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector512(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + bestScore = vectorMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 8: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector256(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + bestScore = vectorMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 4: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector128(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + bestScore = vectorMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + case 2: + { + Mean vectorMean = new Mean(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = calibrateVector64(samples, residualI, residualQ); + vectorMean.increment(score); + } + + if(vectorMean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + } + } + } + + mLog.info("AIRSPY UNPACKED INTERLEAVED - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long calibrateScalar(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorScalar iterator = new AirspyInterleavedBufferIteratorScalar(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector64(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorVector64Bits iterator = new AirspyInterleavedBufferIteratorVector64Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector128(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + for(int x = 0; x < ITERATION_DURATION_MS; x++) + { + AirspyInterleavedBufferIteratorVector128Bits iterator = new AirspyInterleavedBufferIteratorVector128Bits(samples, residualI, + residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector256(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorVector256Bits iterator = + new AirspyInterleavedBufferIteratorVector256Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } + + private long calibrateVector512(short[] samples, short[] residualI, short[] residualQ) + { + long start = System.currentTimeMillis(); + long accumulator = 0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + AirspyInterleavedBufferIteratorVector512Bits iterator = + new AirspyInterleavedBufferIteratorVector512Bits(samples, residualI, residualQ, 0.0f, System.currentTimeMillis()); + + while(iterator.hasNext()) + { + accumulator += iterator.next().samples()[2]; + count++; + } + } + + return count + (accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java new file mode 100644 index 00000000..20ae919f --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/FmDemodulatorCalibration.java @@ -0,0 +1,142 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.demodulator; + +import io.github.dsheirer.dsp.fm.IFmDemodulator; +import io.github.dsheirer.dsp.fm.ScalarFMDemodulator; +import io.github.dsheirer.dsp.fm.VectorFMDemodulator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibrates FM demodulator options + */ +public class FmDemodulatorCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private IFmDemodulator mScalarDemodulator = new ScalarFMDemodulator(); + private IFmDemodulator mVectorDemodulator = new VectorFMDemodulator(); + + + /** + * Constructs an instance + */ + public FmDemodulatorCalibration() + { + super(CalibrationType.FM_DEMODULATOR); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("FM DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("FM DEMODULATOR WARMUP - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + //Start tests + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("FM DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("FM DEMODULATOR - VECTOR: " + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("FM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mScalarDemodulator.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVectorDemodulator.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java new file mode 100644 index 00000000..1380a571 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/SquelchingFmDemodulatorCalibration.java @@ -0,0 +1,148 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.demodulator; + +import io.github.dsheirer.dsp.fm.IFmDemodulator; +import io.github.dsheirer.dsp.fm.ScalarSquelchingFMDemodulator; +import io.github.dsheirer.dsp.fm.VectorSquelchingFMDemodulator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibrates squelching FM demodulator options + */ +public class SquelchingFmDemodulatorCalibration extends Calibration +{ + private static final float POWER_SQUELCH_ALPHA_DECAY = 0.0004f; + private static final float POWER_SQUELCH_THRESHOLD_DB = -78.0f; + private static final int POWER_SQUELCH_RAMP = 4; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IFmDemodulator mScalar = new ScalarSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, + POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + private IFmDemodulator mVector = new VectorSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, + POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP); + + /** + * Constructs an instance + */ + public SquelchingFmDemodulatorCalibration() + { + super(CalibrationType.SQUELCHING_FM_DEMODULATOR); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + //Test begins ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("SQUELCHING FM DEMODULATOR - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("SQUELCHING FM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mScalar.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVector.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java new file mode 100644 index 00000000..f02cc345 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/FirFilterCalibration.java @@ -0,0 +1,378 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.design.FilterDesignException; +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.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibration plugin for FIR filters + */ +public class FirFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealFilter mScalar; + private IRealFilter mVectorPreferred; + private IRealFilter mVector512; + private IRealFilter mVector256; + private IRealFilter mVector128; + private IRealFilter mVector64; + + + /** + * Constructs an instance + */ + public FirFilterCalibration() + { + super(CalibrationType.FILTER_FIR); + + float[] coefficients; + + try + { + coefficients = FilterFactory.getSinc(0.25, 31, WindowType.BLACKMAN); + mScalar = new RealFIRFilter(coefficients); + mVectorPreferred = new VectorRealFIRFilterDefaultBit(coefficients); + mVector64 = new VectorRealFIRFilter64Bit(coefficients); + mVector128 = new VectorRealFIRFilter128Bit(coefficients); + mVector256 = new VectorRealFIRFilter256Bit(coefficients); + mVector512 = new VectorRealFIRFilter512Bit(coefficients); + } + catch(FilterDesignException fde) + { + mLog.error("Error creating sinc filter for real FIR filter calibration"); + } + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + if(mScalar == null) + { + return; + } + + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorPreferredMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("FIR FILTER WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("FIR FILTER - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + vectorPreferredMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + if(vectorPreferredMean.getResult() > bestScore) + { + bestScore = vectorPreferredMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("FIR FILTER - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("FIR FILTER - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0f; + + long start = System.currentTimeMillis(); + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVectorPreferred(float[] samples) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVectorPreferred.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java new file mode 100644 index 00000000..8968aaeb --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealDcRemovalCalibration.java @@ -0,0 +1,144 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.dc.IDcRemovalFilter; +import io.github.dsheirer.dsp.filter.dc.ScalarDcRemovalFilter; +import io.github.dsheirer.dsp.filter.dc.VectorDcRemovalFilter; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Determines optimal DC removal implementation + */ +public class RealDcRemovalCalibration extends Calibration +{ + private static final float GAIN = 0.15f; + private static final int BUFFER_SIZE = 8192; + private static final int ITERATION_DURATION_MS = 1000; + private static final int TEST_ITERATIONS = 5; + private static final int WARMUP_ITERATIONS = 5; + + private IDcRemovalFilter mScalar = new ScalarDcRemovalFilter(GAIN); + private IDcRemovalFilter mVector = new VectorDcRemovalFilter(GAIN); + + /** + * Constructs an instance + */ + public RealDcRemovalCalibration() + { + super(CalibrationType.DC_REMOVAL_REAL); + } + + @Override + public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL DC REMOVAL WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(samples); + vectorMean.increment(score); + } + + mLog.info("REAL DC REMOVAL WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL DC REMOVAL - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(samples); + vectorMean.increment(score); + } + + mLog.info("REAL DC REMOVAL - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("REAL DC REMOVAL - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector.filter(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java new file mode 100644 index 00000000..e0b90a45 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand11TapFilterCalibration.java @@ -0,0 +1,316 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +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.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand11TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand11TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_11_TAP); + float[] coefficients = FilterFactory.getHalfBand(11, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter11Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter11Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter11Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter11Tap64Bit(coefficients); + } + + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 11-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java new file mode 100644 index 00000000..598d2840 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand15TapFilterCalibration.java @@ -0,0 +1,315 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +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.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand15TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1_000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand15TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_15_TAP); + float[] coefficients = FilterFactory.getHalfBand(15, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter15Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter15Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter15Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter15Tap64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 15-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java new file mode 100644 index 00000000..367e14db --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand23TapFilterCalibration.java @@ -0,0 +1,315 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +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.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand23TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand23TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_23_TAP); + float[] coefficients = FilterFactory.getHalfBand(23, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter23Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter23Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter23Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter23Tap64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 23-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java new file mode 100644 index 00000000..edfd5f76 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBand63TapFilterCalibration.java @@ -0,0 +1,315 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +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.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBand63TapFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBand63TapFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_63_TAP); + float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter63Tap512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter63Tap256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter63Tap128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter63Tap64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND 63-TAP DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java new file mode 100644 index 00000000..955997d0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/filter/RealHalfBandDefaultFilterCalibration.java @@ -0,0 +1,361 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.filter; + +import io.github.dsheirer.dsp.filter.FilterFactory; +import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.RealHalfBandDecimationFilter; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter128Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter256Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter512Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilter64Bit; +import io.github.dsheirer.dsp.filter.halfband.VectorRealHalfBandDecimationFilterDefaultBit; +import io.github.dsheirer.dsp.window.WindowType; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Base calibration plugin for real half-band filters + */ +public class RealHalfBandDefaultFilterCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealDecimationFilter mScalar; + private IRealDecimationFilter mVectorPreferred; + private IRealDecimationFilter mVector512; + private IRealDecimationFilter mVector256; + private IRealDecimationFilter mVector128; + private IRealDecimationFilter mVector64; + + /** + * Constructs an instance + */ + public RealHalfBandDefaultFilterCalibration() + { + super(CalibrationType.FILTER_HALF_BAND_REAL_DEFAULT); + float[] coefficients = FilterFactory.getHalfBand(63, WindowType.BLACKMAN); + mScalar = new RealHalfBandDecimationFilter(coefficients); + mVectorPreferred = new VectorRealHalfBandDecimationFilterDefaultBit(coefficients); + mVector512 = new VectorRealHalfBandDecimationFilter512Bit(coefficients); + mVector256 = new VectorRealHalfBandDecimationFilter256Bit(coefficients); + mVector128 = new VectorRealHalfBandDecimationFilter128Bit(coefficients); + mVector64 = new VectorRealHalfBandDecimationFilter64Bit(coefficients); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorPreferredMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + vectorPreferredMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + if(vectorPreferredMean.getResult() > bestScore) + { + bestScore = vectorPreferredMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("REAL HALF-BAND DEFAULT DECIMATE - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mScalar.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVectorPreferred(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVectorPreferred.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector512.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector256.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector128.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] filtered = mVector64.decimateReal(samples); + accumulator += filtered[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java new file mode 100644 index 00000000..d5811b75 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainCalibration.java @@ -0,0 +1,150 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.gain; + +import io.github.dsheirer.dsp.gain.complex.ComplexGain; +import io.github.dsheirer.dsp.gain.complex.ScalarComplexGain; +import io.github.dsheirer.dsp.gain.complex.VectorComplexGain; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Determines the optimal scalar vs vector implementation of complex gain. + */ +public class ComplexGainCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private static final float GAIN = 0.99f; + + /** + * Constructs an instance + */ + public ComplexGainCalibration() + { + super(CalibrationType.GAIN_COMPLEX); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + i = getFloatSamples(BUFFER_SIZE); + q = getFloatSamples(BUFFER_SIZE); + + Mean vectorMean = new Mean(); + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + i = getFloatSamples(BUFFER_SIZE); + q = getFloatSamples(BUFFER_SIZE); + + scalarMean.clear();; + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + i = getFloatSamples(BUFFER_SIZE); + q = getFloatSamples(BUFFER_SIZE); + + vectorMean.clear(); + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX GAIN - SET IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + ComplexGain scalar = new ScalarComplexGain(GAIN); + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = scalar.apply(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + ComplexGain scalar = new VectorComplexGain(GAIN); + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = scalar.apply(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java new file mode 100644 index 00000000..ea5146e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/gain/ComplexGainControlCalibration.java @@ -0,0 +1,143 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.gain; + +import io.github.dsheirer.dsp.gain.complex.ComplexGainControl; +import io.github.dsheirer.dsp.gain.complex.IComplexGainControl; +import io.github.dsheirer.dsp.gain.complex.VectorComplexGainControl; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Determines the optimal scalar vs vector implementation of complex gain control. + */ +public class ComplexGainControlCalibration extends Calibration +{ + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IComplexGainControl mScalarGainControl = new ComplexGainControl(); + private IComplexGainControl mVectorGainControl = new VectorComplexGainControl(); + + /** + * Constructs an instance + */ + public ComplexGainControlCalibration() + { + super(CalibrationType.GAIN_CONTROL_COMPLEX); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(BUFFER_SIZE); + float[] q = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX GAIN CONTROL - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX GAIN CONTROL - SET IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = mScalarGainControl.process(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples amplified = mVectorGainControl.process(i, q); + accumulator += amplified.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java new file mode 100644 index 00000000..0a71df56 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/hilbert/HilbertCalibration.java @@ -0,0 +1,350 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.hilbert; + +import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; +import io.github.dsheirer.dsp.filter.hilbert.ScalarHilbertTransform; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform128Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform256Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform512Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransform64Bits; +import io.github.dsheirer.dsp.filter.hilbert.VectorHilbertTransformDefaultBits; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorSpecies; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +public class HilbertCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private HilbertTransform mScalar = new ScalarHilbertTransform(); + private HilbertTransform mVectorPreferred = new VectorHilbertTransformDefaultBits(); + private HilbertTransform mVector512 = new VectorHilbertTransform512Bits(); + private HilbertTransform mVector256 = new VectorHilbertTransform256Bits(); + private HilbertTransform mVector128 = new VectorHilbertTransform128Bits(); + private HilbertTransform mVector64 = new VectorHilbertTransform64Bits(); + + /** + * Constructs an instance + */ + public HilbertCalibration() + { + super(CalibrationType.HILBERT_TRANSFORM); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("HILBERT WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorPreferredMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + Mean vector512Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 16) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + } + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + } + + Mean vector128Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 4) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + } + + Mean vector64Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 2) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("HILBERT WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("HILBERT - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + vectorPreferredMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVectorPreferred(samples); + vectorPreferredMean.increment(score); + } + + mLog.info("HILBERT - VECTOR PREFERRED: " + DECIMAL_FORMAT.format(vectorPreferredMean.getResult())); + + if(vectorPreferredMean.getResult() > bestScore) + { + bestScore = vectorPreferredMean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + if(VECTOR_SPECIES.length() >= 16) + { + vector512Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vector512Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult())); + + if(vector512Mean.getResult() > bestScore) + { + bestScore = vector512Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_512); + } + } + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vector256Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult())); + + if(vector256Mean.getResult() > bestScore) + { + bestScore = vector256Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_256); + } + } + + if(VECTOR_SPECIES.length() >= 4) + { + vector128Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vector128Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult())); + + if(vector128Mean.getResult() > bestScore) + { + bestScore = vector128Mean.getResult(); + setImplementation(Implementation.VECTOR_SIMD_128); + } + } + + if(VECTOR_SPECIES.length() >= 2) + { + vector64Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vector64Mean.increment(score); + } + + mLog.info("HILBERT - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("HILBERT - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mScalar.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVectorPreferred(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVectorPreferred.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector512.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector256.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector128.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples) + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples complex = mVector64.filter(samples); + accumulator += complex.i()[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java new file mode 100644 index 00000000..9bc312fe --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/mixer/ComplexMixerCalibration.java @@ -0,0 +1,141 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.mixer; + +import io.github.dsheirer.dsp.mixer.ScalarComplexMixer; +import io.github.dsheirer.dsp.mixer.VectorComplexMixer; +import io.github.dsheirer.sample.complex.ComplexSamples; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibrates the complex mixer implementations to determine the optimal instance. + */ +public class ComplexMixerCalibration extends Calibration +{ + private static final double FREQUENCY = 2.0; + private static final double SAMPLE_RATE = 10.0; + private static final int SAMPLE_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private ScalarComplexMixer mScalarComplexMixer = new ScalarComplexMixer(FREQUENCY, SAMPLE_RATE); + private VectorComplexMixer mVectorComplexMixer = new VectorComplexMixer(FREQUENCY, SAMPLE_RATE); + + /** + * Constructs an instance + */ + public ComplexMixerCalibration() + { + super(CalibrationType.MIXER_COMPLEX); + } + + @Override public void calibrate() throws CalibrationException + { + float[] i = getFloatSamples(SAMPLE_SIZE); + float[] q = getFloatSamples(SAMPLE_SIZE); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX MIXER WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX MIXER WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("COMPLEX MIXER - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(i, q); + vectorMean.increment(score); + } + + mLog.info("COMPLEX MIXER - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX MIXER - IMPLEMENTATION SET TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples mixed = mScalarComplexMixer.mix(i, q); + accumulator += mixed.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector(float[] i, float[] q) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + ComplexSamples mixed = mVectorComplexMixer.mix(i, q); + accumulator += mixed.i()[2]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java new file mode 100644 index 00000000..68a1e233 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/ComplexOscillatorCalibration.java @@ -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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.oscillator; + +import io.github.dsheirer.dsp.oscillator.IComplexOscillator; +import io.github.dsheirer.dsp.oscillator.ScalarComplexOscillator; +import io.github.dsheirer.dsp.oscillator.VectorComplexOscillator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibration plugin for complex oscillators + */ +public class ComplexOscillatorCalibration extends Calibration +{ + private static final double FREQUENCY = 5.0d; + private static final double SAMPLE_RATE = 100.0d; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IComplexOscillator mScalarOscillator = new ScalarComplexOscillator(FREQUENCY, SAMPLE_RATE); + private IComplexOscillator mVectorOscillator = new VectorComplexOscillator(FREQUENCY, SAMPLE_RATE); + + + /** + * Constructs an instance + */ + public ComplexOscillatorCalibration() + { + super(CalibrationType.OSCILLATOR_COMPLEX); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("COMPLEX OSCILLATOR - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("COMPLEX OSCILLATOR - SETTING OPTIMAL OPERATION TO:" + getImplementation()); + } + + /** + * Calculates the time duration needed to generate the sample buffers of the specified size and iteration count + * @return time duration in milliseconds + */ + private long testScalar() + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mScalarOscillator.generate(BUFFER_SIZE); + accumulator += generated[0]; + count++; + } + + return count + (long)(accumulator * 0); + } + + /** + * Calculates the time duration to process the sample buffer with the filter coefficients. + * @return time duration in milliseconds + */ + private long testVector() + { + float accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mVectorOscillator.generate(BUFFER_SIZE); + accumulator += generated[0]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java new file mode 100644 index 00000000..ffa0d619 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/oscillator/RealOscillatorCalibration.java @@ -0,0 +1,146 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.oscillator; + +import io.github.dsheirer.dsp.oscillator.IRealOscillator; +import io.github.dsheirer.dsp.oscillator.ScalarRealOscillator; +import io.github.dsheirer.dsp.oscillator.VectorRealOscillator; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calibration plugin for real oscillators + */ +public class RealOscillatorCalibration extends Calibration +{ + private static final double FREQUENCY = 5.0d; + private static final double SAMPLE_RATE = 100.0d; + private static final int BUFFER_SIZE = 2048; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private IRealOscillator mScalar = new ScalarRealOscillator(FREQUENCY, SAMPLE_RATE); + private IRealOscillator mVector = new VectorRealOscillator(FREQUENCY, SAMPLE_RATE); + + /** + * Constructs an instance + */ + public RealOscillatorCalibration() + { + super(CalibrationType.OSCILLATOR_REAL); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("REAL OSCILLATOR WARMUP - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("REAL OSCILLATOR WARMUP - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + //Test begins + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(); + scalarMean.increment(score); + } + + mLog.info("REAL OSCILLATOR - SCALAR:" + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector(); + vectorMean.increment(score); + } + + mLog.info("REAL OSCILLATOR - VECTOR:" + DECIMAL_FORMAT.format(vectorMean.getResult())); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("REAL OSCILLATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar() + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mScalar.generate(BUFFER_SIZE); + accumulator += generated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector() + { + double accumulator = 0.0; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] generated = mVector.generate(BUFFER_SIZE); + accumulator += generated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java new file mode 100644 index 00000000..68653620 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/window/WindowCalibration.java @@ -0,0 +1,144 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.vector.calibrate.window; + +import io.github.dsheirer.dsp.window.ScalarWindow; +import io.github.dsheirer.dsp.window.VectorWindow; +import io.github.dsheirer.dsp.window.Window; +import io.github.dsheirer.dsp.window.WindowFactory; +import io.github.dsheirer.vector.calibrate.Calibration; +import io.github.dsheirer.vector.calibrate.CalibrationException; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; +import org.apache.commons.math3.stat.descriptive.moment.Mean; + +/** + * Calculates the optimal (scalar vs vector) implementation for windowing samples. + */ +public class WindowCalibration extends Calibration +{ + private static final int WINDOW_SIZE = 8192; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + private Window mScalarWindow; + private Window mVectorWindow; + + /** + * Constructs an instance + */ + public WindowCalibration() + { + super(CalibrationType.WINDOW); + float[] window = WindowFactory.getBlackman(WINDOW_SIZE); + mScalarWindow = new ScalarWindow(window); + mVectorWindow = new VectorWindow(window); + } + + @Override public void calibrate() throws CalibrationException + { + Mean scalarMean = new Mean(); + + float[] samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + scalarMean.increment(testScalar(samples)); + } + + mLog.info("WINDOW WARMUP - SCALAR: " + scalarMean.getResult()); + + Mean vectorMean = new Mean(); + + samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + vectorMean.increment(testVector(samples)); + } + + mLog.info("WINDOW WARMUP - VECTOR: " + vectorMean.getResult()); + + //Test starts ... + scalarMean.clear(); + + samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + scalarMean.increment(testScalar(samples)); + } + + mLog.info("WINDOW - SCALAR: " + scalarMean.getResult()); + + vectorMean.clear(); + + samples = getFloatSamples(WINDOW_SIZE); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + vectorMean.increment(testVector(samples)); + } + + mLog.info("WINDOW - VECTOR: " + vectorMean.getResult()); + + if(scalarMean.getResult() > vectorMean.getResult()) + { + setImplementation(Implementation.SCALAR); + } + else + { + setImplementation(Implementation.VECTOR_SIMD_PREFERRED); + } + + mLog.info("WINDOW - OPTIMAL IMPLEMENTATION SET TO: " + getImplementation()); + } + + private long testScalar(float[] samples) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0d; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + mScalarWindow.apply(samples); + accumulator += samples[3]; + count++; + } + + return count + (long)(accumulator * 0); + } + private long testVector(float[] samples) + { + long start = System.currentTimeMillis(); + double accumulator = 0.0d; + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + mVectorWindow.apply(samples); + accumulator += samples[3]; + count++; + } + + return count + (long)(accumulator * 0); + } +}