diff --git a/build.gradle b/build.gradle index 48509d72..6c94270e 100644 --- a/build.gradle +++ b/build.gradle @@ -164,6 +164,15 @@ application { } } +test { + if(os.isWindows()) { + jvmArgs = jvmArgsWindows + } + else { + jvmArgs = jvmArgsLinux + } +} + jar { manifest { attributes ( diff --git a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java index 341798e7..5d887c89 100644 --- a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java +++ b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java @@ -281,6 +281,20 @@ public class BinaryMessage extends BitSet } } + /** + * Adds bit1 and bit2 to this bitset. + * + * Note: this method does not provide overfill protection. + * + * @param bit1 to add. + * @param bit2 to add. + */ + public void add(boolean bit1, boolean bit2) + { + set(mPointer++, bit1); + set(mPointer++, bit2); + } + public String toString() { StringBuilder sb = new StringBuilder(); 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 21d31990..fc754be6 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-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,15 +56,14 @@ 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 java.text.DecimalFormat; +import java.util.Set; +import java.util.TreeSet; 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.Set; -import java.util.TreeSet; - /** * Utility methods for designing various types of filters. */ @@ -627,11 +626,13 @@ public class FilterFactory * @param samplesPerSymbol - number of samples per symbol * @param symbolCount - number of symbol intervals for the filter. This directly impacts the filter size * @param alpha - roll-off factor. + * @param gain of the filter * @return - filter coefficients */ public static float[] getRootRaisedCosine(double samplesPerSymbol, int symbolCount, float alpha) { - int taps = (int)(samplesPerSymbol * symbolCount); + samplesPerSymbol /= 2; + int taps = (int)Math.round(samplesPerSymbol * symbolCount); float scale = 0; @@ -639,47 +640,47 @@ public class FilterFactory for(int x = 0; x < taps; x++) { - float index = (float)x - ((float)taps / 2.0f); + double index = x - (taps / 2.0f); - float x1 = (float)FastMath.PI * index / (float)samplesPerSymbol; - float x2 = 4.0f * alpha * index / (float)samplesPerSymbol; - float x3 = x2 * x2 - 1.0f; + double x1 = FastMath.PI * index / samplesPerSymbol; + double x2 = 4.0 * alpha * index / samplesPerSymbol; + double x3 = x2 * x2 - 1.0; - float numerator, denominator; + double numerator, denominator; if(FastMath.abs(x3) >= 0.000001) { if(x != taps / 2) { - numerator = (float)FastMath.cos((1.0 + alpha) * x1) + - (float)FastMath.sin((1.0f - alpha) * x1) / (4.0f * alpha * index / (float)samplesPerSymbol); + numerator = FastMath.cos((1.0 + alpha) * x1) + + FastMath.sin((1.0 - alpha) * x1) / (4.0 * alpha * index / samplesPerSymbol); } else { - numerator = (float)FastMath.cos((1.0f + alpha) * x1) + (1.0f - alpha) * (float)FastMath.PI / (4.0f * alpha); + numerator = FastMath.cos((1.0 + alpha) * x1) + (1.0 - alpha) * FastMath.PI / (4.0 * alpha); } - denominator = x3 * (float)FastMath.PI; + denominator = x3 * FastMath.PI; } else { - if(alpha == 1.0f) + if(alpha == 1.0) { coefficients[x] = -1.0f; continue; } - x3 = (1.0f - alpha) * x1; - x2 = (1.0f + alpha) * x1; + x3 = (1.0 - alpha) * x1; + x2 = (1.0 + alpha) * x1; - numerator = (float)(FastMath.sin(x2) * (1.0f + alpha) * FastMath.PI - FastMath.cos(x3) * ((1.0 - alpha) * FastMath.PI * - (double)samplesPerSymbol) / (4.0 * alpha * index) + FastMath.sin(x3) * (double)samplesPerSymbol * - (double)samplesPerSymbol / (4.0 * alpha * index * index)); + numerator = (FastMath.sin(x2) * (1.0 + alpha) * FastMath.PI - FastMath.cos(x3) * ((1.0 - alpha) * FastMath.PI * + samplesPerSymbol) / (4.0 * alpha * index) + FastMath.sin(x3) * samplesPerSymbol * + samplesPerSymbol / (4.0 * alpha * index * index)); - denominator = (float)(-32.0 * FastMath.PI * alpha * alpha * index / (double)samplesPerSymbol); + denominator = -32.0 * FastMath.PI * alpha * alpha * index / samplesPerSymbol; } - coefficients[x] = 4.0f * alpha * numerator / denominator; + coefficients[x] = (float)(4.0 * alpha * numerator / denominator); scale += coefficients[x]; } @@ -1210,13 +1211,20 @@ public class FilterFactory { DecimalFormat df = new DecimalFormat("0.000000"); - int length = 15; + int length = 49; - float [] taps = FilterFactory.getHalfBand(length, WindowType.HAMMING); - - for(int x = 0; x < length; x++) + try { - mLog.debug("Tap: " + x + " Value: " + df.format(taps[x])); + float [] taps = FilterFactory.getSinc(0.5, length, WindowType.NONE); + + for(int x = 0; x < length; x++) + { + mLog.debug("Tap: " + x + " Value: " + df.format(taps[x])); + } + } + catch(Exception e) + { + e.printStackTrace(); } mLog.debug("Done!"); 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 a587b659..7dd7ac32 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-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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.dsp.filter.design; import io.github.dsheirer.dsp.filter.FilterFactory; -import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; import io.github.dsheirer.dsp.window.WindowType; import javafx.application.Application; import javafx.scene.Scene; @@ -62,9 +61,10 @@ public class FilterViewer extends Application int filterLength = 47; WindowType windowType = WindowType.HAMMING; - float[] taps = FilterFactory.getHalfBand(filterLength, windowType); +// float[] taps = FilterFactory.getHalfBand(filterLength, windowType); + float[] taps = FilterFactory.getRootRaisedCosine(50000.0 / 4800.0, 20, 0.2f); - taps = HilbertTransform.HALF_BAND_FILTER_47_TAP; +// taps = HilbertTransform.HALF_BAND_FILTER_47_TAP; if(taps == null) { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java index a0b1e8e4..295e8edd 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java @@ -1,25 +1,24 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2015 Dennis Sheirer - * - * Copyright (C) 2002,2012 Free Software Foundation, Inc. - * Ported to java from gnuradio - interpolator_taps.h - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - ******************************************************************************/ public abstract class Interpolator { public static final int NTAPS = 8; @@ -158,4 +157,19 @@ public abstract class Interpolator { -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 }; + + /** + * Calculates an interpolated value from eight samples that start at the offset into the sample array. The + * interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and + * offset+4. The mu argument is translated into an index position between 0 and 128, where a mu of 0.0 will be + * converted to index zero and will be equal to the sample at index offset+3 and a mu of 1.0 will be equal to + * the sample at offset+4. All mu values between 0.0 and 1.0 will be converted to a 1 - 127 index and will + * produce an approximated value from among 127 interpolated sample points between indexes offset+3 and offset+4. + * + * @param samples - sample array of length at least offset + 7 + * @param offset into the sample array for the start of the 8-value sequence + * @param mu - interpolated sample position between 0.0 and 1.0 + * @return - interpolated sample value + */ + public abstract float filter(float[] samples, int offset, float mu); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorFactory.java new file mode 100644 index 00000000..f738b6c5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorFactory.java @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; + +import io.github.dsheirer.vector.calibrate.CalibrationManager; +import io.github.dsheirer.vector.calibrate.CalibrationType; +import io.github.dsheirer.vector.calibrate.Implementation; + +/** + * Factory for creating an optimal scalar or vector (SIMD) interpolator implementation + */ +public class InterpolatorFactory +{ + /** + * Selects and instantiates the best interpolator version, scalar or vector, based on previous calibration. + * @return interpolator + */ + public static Interpolator getInterpolator() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.INTERPOLATOR); + return getInterpolator(implementation); + } + + /** + * Instantiates the specified interpolator implementation. + * @param implementation to construct. + * @return interpolator. + */ + public static Interpolator getInterpolator(Implementation implementation) + { + return switch(implementation) + { + case VECTOR_SIMD_64 -> new InterpolatorVector64(); + case VECTOR_SIMD_128 -> new InterpolatorVector128(); + case VECTOR_SIMD_256, VECTOR_SIMD_512 -> new InterpolatorVector256(); //Vector 512 not supported + case SCALAR, UNCALIBRATED -> new InterpolatorScalar(); + default -> throw new IllegalArgumentException("Unknown implementation type: " + implementation); + }; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorScalar.java similarity index 72% rename from src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java rename to src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorScalar.java index 00a64f80..73215e97 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorScalar.java @@ -1,17 +1,35 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; import io.github.dsheirer.sample.complex.Complex; +import java.text.DecimalFormat; +import java.util.Arrays; import org.apache.commons.lang3.Validate; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DecimalFormat; -import java.util.Arrays; - -public class RealInterpolator extends Interpolator +public class InterpolatorScalar extends Interpolator { - private final static Logger mLog = LoggerFactory.getLogger(RealInterpolator.class); + private final static Logger mLog = LoggerFactory.getLogger(InterpolatorScalar.class); private float mGain; @@ -21,11 +39,16 @@ public class RealInterpolator extends Interpolator * * @param gain to apply to the interpolated sample */ - public RealInterpolator(float gain) + public InterpolatorScalar(float gain) { mGain = gain; } + public InterpolatorScalar() + { + this(1.0f); + } + /** * Calculates an interpolated value from eight samples that start at the offset into the sample array. The * interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and @@ -68,7 +91,7 @@ public class RealInterpolator extends Interpolator public static void main(String[] args) { - RealInterpolator interpolator = new RealInterpolator(1.0f); + InterpolatorScalar interpolator = new InterpolatorScalar(1.0f); DecimalFormat decimalFormat = new DecimalFormat("0.0000"); double TWO_PI = FastMath.PI * 2.0; diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector128.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector128.java new file mode 100644 index 00000000..0c54160b --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector128.java @@ -0,0 +1,49 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector interpolator for 128 bit SIMD instructions. + */ +public class InterpolatorVector128 extends VectorInterpolator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + @Override + protected float vectorFilter(float[] samples, int offset, int index) + { + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + FloatVector tapsVector, sampleVector; + + tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0); + sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset); + accumulator = tapsVector.fma(sampleVector, accumulator); + + tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 4); + sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 4); + accumulator = tapsVector.fma(sampleVector, accumulator); + + return accumulator.reduceLanes(VectorOperators.ADD); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector256.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector256.java new file mode 100644 index 00000000..6b137a63 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector256.java @@ -0,0 +1,40 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector interpolator for 256 bit SIMD instructions. + */ +public class InterpolatorVector256 extends VectorInterpolator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + @Override + protected float vectorFilter(float[] samples, int offset, int index) + { + FloatVector tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0); + FloatVector sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset); + return sampleVector.mul(tapsVector).reduceLanes(VectorOperators.ADD); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector64.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector64.java new file mode 100644 index 00000000..a97b4dd1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector64.java @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * Vector interpolator for 64 bit SIMD instructions. + */ +public class InterpolatorVector64 extends VectorInterpolator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + @Override + protected float vectorFilter(float[] samples, int offset, int index) + { + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + FloatVector tapsVector, sampleVector; + + tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0); + sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset); + accumulator = tapsVector.fma(sampleVector, accumulator); + + tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 2); + sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 2); + accumulator = tapsVector.fma(sampleVector, accumulator); + + tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 4); + sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 4); + accumulator = tapsVector.fma(sampleVector, accumulator); + + tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 6); + sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 6); + accumulator = tapsVector.fma(sampleVector, accumulator); + + return accumulator.reduceLanes(VectorOperators.ADD); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java new file mode 100644 index 00000000..21f23fc8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java @@ -0,0 +1,74 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; + +/** + * Linear interpolator for values in the range: -PI > 0 > PI with awareness of phase wrapping across the + * (+/-) PI boundary. + */ +public class PhaseAwareLinearInterpolator +{ + public static final float TWO_PI = (float)(Math.PI * 2.0); + + /** + * Calculates an interpolated value between x1 and x2 at a linear position mu between 0.0 and 1.0 + * @param x1 first value + * @param x2 second value + * @param mu offset between first and second values in range 0.0 to 1.0 + * @return interpolated value. + */ + public static float calculate(float x1, float x2, float mu) + { + //Detect phase wrap at the +PI/-PI boundary ... ignore opposite side of the unit circle at 0 axis + if(x1 * x2 < -Math.PI) + { + if(x1 > 0) + { + float value = x1 + ((TWO_PI + x2 - x1) * mu); + + if(value > Math.PI) + { + return -TWO_PI + value; + } + else + { + return value; + } + } + else + { + float value = x1 + ((-TWO_PI + x2 - x1) * mu); + + if(value < -Math.PI) + { + return TWO_PI + value; + } + else + { + return value; + } + } + } + else + { + return x1 + ((x2 - x1) * mu); + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/VectorInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/VectorInterpolator.java new file mode 100644 index 00000000..a7f813b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/VectorInterpolator.java @@ -0,0 +1,50 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.dsp.filter.interpolator; + +import org.apache.commons.lang3.Validate; + +/** + * Base vector interpolator implementation. + */ +public abstract class VectorInterpolator extends Interpolator +{ + @Override + public float filter(float[] samples, int offset, float mu) + { + /* Ensure we have enough samples in the array */ + Validate.isTrue(samples.length >= offset + 7, "Offset [" + offset + "] must be 7 less than length[" + samples.length + "]"); + + //Identify the filter bank that corresponds to mu. Note: since we're not loading the TAPS in reverse order, + //we select the inverse tap index that has the mirrored set of taps, by subtracting from 127. + int index = 127 - (int)(NSTEPS * mu); + return vectorFilter(samples, offset, index); + } + + /** + * Perform vectorized interpolation to find the interpolated value from the samples where the 8-value sequence + * starts at the offset into the array, using the filter coefficients specified by the index value. + * @param samples to interpolate + * @param offset to the start of the 8-value sequence + * @param index into the TAPS coefficients array. + * @return interpolated value. + */ + protected abstract float vectorFilter(float[] samples, int offset, int index); +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java index 7eebc342..08b30d52 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java @@ -1,23 +1,30 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public - * 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.filter.interpolator.Interpolator; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorFactory; +import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.sample.complex.Complex; +import java.text.DecimalFormat; public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator { @@ -29,6 +36,11 @@ public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator private Complex mPrecedingSymbol = new Complex(0, 0); protected Complex mCurrentSymbol = new Complex(0, 0); + private final Interpolator mInterpolator = InterpolatorFactory.getInterpolator(); + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("+#0.00000;-#0.00000"); + private float[] mI = new float[15]; + private float[] mQ = new float[15]; + /** * Decoder for Differential Quaternary Phase Shift Keying (DQPSK). This decoder uses both a Costas Loop (PLL) and * a decision-directed DQPSK symbol timing and phase error detector to automatically align to the incoming carrier @@ -44,6 +56,32 @@ public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator super(interpolatingSampleBuffer, phaseLockedLoop); } + @Override + public void receive(float inphase, float quadrature) + { + super.receive(inphase, quadrature); + + System.arraycopy(mI, 1, mI, 0, mI.length - 1); + System.arraycopy(mQ, 1, mQ, 0, mQ.length - 1); + + //Rotate the sample and add to buffer + Complex current = new Complex(inphase, quadrature); + current.normalizeFast(); + current.multiply(getPLL().getCurrentVector()); + mI[mI.length - 1] = current.inphase(); + mQ[mQ.length - 1] = current.quadrature(); + float iTemp = mInterpolator.filter(mI, 6, .416f); + float qTemp = mInterpolator.filter(mQ, 6, .416f); + float iSymbol = (mI[0] * iTemp) - (-mQ[0] * qTemp); + float qSymbol = (mI[0] * qTemp) + (iTemp * -mQ[0]); + float radians = (float)Math.atan2(iSymbol, qSymbol); + + StringBuilder sb = new StringBuilder(); + sb.append(DECIMAL_FORMAT.format(radians)).append(","); + sb.append(DECIMAL_FORMAT.format(((CostasLoop)getPLL()).getLoopFrequency())); +// System.out.println(sb); + } + /** * Calculates a symbol from the interpolating buffer */ @@ -79,7 +117,7 @@ public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator getInterpolatingSampleBuffer().resetAndAdjust(mSymbolEvaluator.getTimingError()); //Update the costas loop (PLL) with any measured phase error - getPLL().adjust(clip(mSymbolEvaluator.getPhaseError(), 0.5f)); +// getPLL().adjust(clip(mSymbolEvaluator.getPhaseError(), 0.5f)); //Store current samples/symbols to use for the next symbol period mPreviousPrecedingSample.setValues(mPrecedingSample); diff --git a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java index 6d52579c..91fc440f 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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,7 +18,7 @@ */ package io.github.dsheirer.dsp.psk; -import io.github.dsheirer.dsp.filter.interpolator.RealInterpolator; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorScalar; import io.github.dsheirer.sample.complex.Complex; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; @@ -45,7 +45,7 @@ public class InterpolatingSampleBuffer private float mMaximumSamplesPerSymbol; private float mMinimumSamplesPerSymbol; - private RealInterpolator mInterpolator = new RealInterpolator(1.0f); + private InterpolatorScalar mInterpolator = new InterpolatorScalar(1.0f); /** * Buffer to store complex sample data and produce interpolated samples. diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java new file mode 100644 index 00000000..b95f1ba7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java @@ -0,0 +1,64 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import io.github.dsheirer.dsp.filter.interpolator.Interpolator; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorFactory; +import io.github.dsheirer.dsp.fm.IDemodulator; + +/** + * DQPSK demodulator base class + */ +public abstract class DQPSKDemodulator implements IDemodulator +{ + private int mSymbolRate; + private float mSampleRate; + private float mSamplesPerSymbol; + protected float[] mIBuffer = new float[20]; //Initial size 20 for array copy, but gets resized on first buffer + protected float[] mQBuffer = new float[20]; + protected float mMu; + protected int mBufferOverlap; + protected int mInterpolationOffset; + protected final Interpolator mInterpolator = InterpolatorFactory.getInterpolator(); + + /** + * Constructor + * @param sampleRate in Hertz + * @param symbolRate symbols per second + */ + public DQPSKDemodulator(double sampleRate, int symbolRate) + { + mSampleRate = (float) sampleRate; + mSymbolRate = symbolRate; + mSamplesPerSymbol = mSampleRate / mSymbolRate; + mMu = mSamplesPerSymbol % 1; //Fractional part of the samples per symbol rate + mInterpolationOffset = (int) Math.floor(mSamplesPerSymbol) - 4; //Interpolate at the middle of 8x samples + mBufferOverlap = (int) Math.floor(mSamplesPerSymbol) + 4; + } + + /** + * Calculated samples per symbol. + * @return samples per symbol. + */ + public float getSamplesPerSymbol() + { + return mSamplesPerSymbol; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java new file mode 100644 index 00000000..661cbcdc --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java @@ -0,0 +1,55 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +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 DQPSK demodulators + */ +public class DQPSKDemodulatorFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(DQPSKDemodulatorFactory.class); + + /** + * Creates the optimal demodulator using calibration data to select the optimal implementation from scalar and + * vector options, or defaults to the scalar implementation if the calibration wasn't executed. + * @param sampleRate in Hertz + * @param symbolRate in Hertz + * @return demodulator instance + */ + public static DQPSKDemodulator getDemodulator(double sampleRate, int symbolRate) + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DQPSK_DEMODULATOR); + + return switch(implementation) + { + case VECTOR_SIMD_64 -> new DQPSKDemodulatorVector64(sampleRate, symbolRate); + case VECTOR_SIMD_128 -> new DQPSKDemodulatorVector128(sampleRate, symbolRate); + case VECTOR_SIMD_256 -> new DQPSKDemodulatorVector256(sampleRate, symbolRate); + case VECTOR_SIMD_512 -> new DQPSKDemodulatorVector512(sampleRate, symbolRate); + default -> new DQPSKDemodulatorScalar(sampleRate, symbolRate); //Includes SCALAR case + }; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java new file mode 100644 index 00000000..9e6ba4a1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import java.util.Arrays; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DQPSK demodulator that uses scalar calculations for demodulating the sample stream. + */ +public class DQPSKDemodulatorScalar extends DQPSKDemodulator +{ + private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorScalar.class); + + /** + * Constructor + * + * @param symbolRate symbols per second + */ + public DQPSKDemodulatorScalar(double sampleRate, int symbolRate) + { + super(sampleRate, symbolRate); + } + + @Override + public float[] demodulate(float[] i, float[] q) + { + int sampleLength = i.length; + int bufferOverlap = mBufferOverlap; + float mu = mMu; + + //Copy previous buffer residual samples to beginning of buffer. + System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap); + + //Resize I/Q buffers if necessary + int requiredBufferLength = sampleLength + bufferOverlap; + if(mIBuffer.length != requiredBufferLength) + { + mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength); + mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength); + } + + //Append new samples to the residual samples from the previous buffer. + System.arraycopy(i, 0, mIBuffer, bufferOverlap, sampleLength); + System.arraycopy(q, 0, mQBuffer, bufferOverlap, sampleLength); + //mIDecoded, mQDecoded and mPhase will be filled below during the decoding process. + + float[] decodedPhases = new float[i.length]; + float iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ; + + //Differential demodulation. + for(int x = 0; x < sampleLength; x++) + { + iPrevious = mIBuffer[x]; + qPreviousConjugate = -mQBuffer[x]; //Complex Conjugate + + int offset = mInterpolationOffset + x; + iCurrent = mInterpolator.filter(mIBuffer, offset, mu); + qCurrent = mInterpolator.filter(mQBuffer, offset, mu); + + //Multiply current complex sample by the complex conjugate of previous complex sample. + differentialI = (iPrevious * iCurrent) - (qPreviousConjugate * qCurrent); + differentialQ = (iPrevious * qCurrent) + (iCurrent * qPreviousConjugate); + + decodedPhases[x] = (float)Math.atan2(differentialQ, differentialI); + } + + return decodedPhases; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java new file mode 100644 index 00000000..15a53092 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import java.util.Arrays; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DQPSK demodulator that uses Vector SIMD 128 calculations for demodulating the sample stream. + */ +public class DQPSKDemodulatorVector128 extends DQPSKDemodulator +{ + private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector128.class); + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + /** + * Constructor + * + * @param sampleRate in Hertz + * @param symbolRate symbols per second + */ + public DQPSKDemodulatorVector128(double sampleRate, int symbolRate) + { + super(sampleRate, symbolRate); + } + + @Override + public float[] demodulate(float[] i, float[] q) + { + int sampleLength = i.length; + int bufferOverlap = mBufferOverlap; + float mu = mMu; + + //Copy previous buffer residual samples to beginning of buffer. + System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap); + + //Resize I/Q buffers if necessary + int requiredBufferLength = sampleLength + bufferOverlap; + if(mIBuffer.length != requiredBufferLength) + { + if(i.length % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length()); + } + mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength); + mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength); + } + + //Append new samples to the residual samples from the previous buffer. + System.arraycopy(i, 0, mIBuffer, bufferOverlap, sampleLength); + System.arraycopy(q, 0, mQBuffer, bufferOverlap, sampleLength); + //mIDecoded, mQDecoded and mPhase will be filled below during the decoding process. + + float[] interpolatedI = new float[VECTOR_SPECIES.length()]; + float[] interpolatedQ = new float[VECTOR_SPECIES.length()]; + float[] decodedPhases = new float[i.length]; + FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ; + + //Differential demodulation. + for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length()) + { + iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x); + qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate + + int offset = mInterpolationOffset + x; + int index; + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + index = offset + y; + interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu); + interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu); + } + iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0); + qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0); + + //Multiply current complex sample by the complex conjugate of previous complex sample. + differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent)); + differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate)); + + //Calculate phase angles using Arc Tangent and export to the decoded phases array. + differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x); + } + + return decodedPhases; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java new file mode 100644 index 00000000..a36cb25c --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java @@ -0,0 +1,105 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import java.util.Arrays; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * DQPSK demodulator that uses Vector SIMD calculations for demodulating the sample stream. + */ +public class DQPSKDemodulatorVector256 extends DQPSKDemodulator +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + /** + * Constructor + * + * @param sampleRate in Hertz + * @param symbolRate symbols per second + */ + public DQPSKDemodulatorVector256(double sampleRate, int symbolRate) + { + super(sampleRate, symbolRate); + } + + @Override + public float[] demodulate(float[] i, float[] q) + { + int sampleLength = i.length; + int bufferOverlap = mBufferOverlap; + float mu = mMu; + + //Copy previous buffer residual samples to beginning of buffer. + System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap); + + //Resize I/Q buffers if necessary + int requiredBufferLength = sampleLength + bufferOverlap; + if(mIBuffer.length != requiredBufferLength) + { + if(i.length % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length()); + } + mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength); + mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength); + } + + //Append new samples to the residual samples from the previous buffer. + System.arraycopy(i, 0, mIBuffer, bufferOverlap, sampleLength); + System.arraycopy(q, 0, mQBuffer, bufferOverlap, sampleLength); + //mIDecoded, mQDecoded and mPhase will be filled below during the decoding process. + + float[] interpolatedI = new float[VECTOR_SPECIES.length()]; + float[] interpolatedQ = new float[VECTOR_SPECIES.length()]; + float[] decodedPhases = new float[sampleLength]; + FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ; + + //Differential demodulation. + for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length()) + { + iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x); + qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate + + int offset = mInterpolationOffset + x; + int index; + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + index = offset + y; + interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu); + interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu); + } + iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0); + qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0); + + //Multiply current complex sample by the complex conjugate of previous complex sample. + differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent)); + differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate)); + + //Calculate phase angles using Arc Tangent and export to the decoded phases array. + differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x); + } + + return decodedPhases; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java new file mode 100644 index 00000000..b1ab9698 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java @@ -0,0 +1,106 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import java.util.Arrays; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DQPSK demodulator that uses Vector SIMD 512 calculations for demodulating the sample stream. + */ +public class DQPSKDemodulatorVector512 extends DQPSKDemodulator +{ + private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector512.class); + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + + /** + * Constructor + * + * @param symbolRate symbols per second + */ + public DQPSKDemodulatorVector512(double sampleRate, int symbolRate) + { + super(sampleRate, symbolRate); + } + + @Override + public float[] demodulate(float[] i, float[] q) + { + int sampleLength = i.length; + int bufferOverlap = mBufferOverlap; + float mu = mMu; + + //Copy previous buffer residual samples to beginning of buffer. + System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap); + + //Resize I/Q buffers if necessary + int requiredBufferLength = sampleLength + bufferOverlap; + if(mIBuffer.length != requiredBufferLength) + { + if(i.length % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length()); + } + mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength); + mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength); + } + + //Append new samples to the residual samples from the previous buffer. + System.arraycopy(i, 0, mIBuffer, bufferOverlap, sampleLength); + System.arraycopy(q, 0, mQBuffer, bufferOverlap, sampleLength); + + float[] interpolatedI = new float[VECTOR_SPECIES.length()]; + float[] interpolatedQ = new float[VECTOR_SPECIES.length()]; + float[] decodedPhases = new float[sampleLength]; + FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ; + + //Differential demodulation. + for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length()) + { + iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x); + qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate + + int offset = mInterpolationOffset + x; + int index; + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + index = offset + y; + interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu); + interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu); + } + iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0); + qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0); + + //Multiply current complex sample by the complex conjugate of previous complex sample. + differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent)); + differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate)); + + //Calculate phase angles using Arc Tangent and export to the decoded phases array. + differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x); + } + + return decodedPhases; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java new file mode 100644 index 00000000..63d09676 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import java.util.Arrays; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DQPSK demodulator that uses Vector SIMD 64 calculations for demodulating the sample stream. + */ +public class DQPSKDemodulatorVector64 extends DQPSKDemodulator +{ + private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector64.class); + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + /** + * Constructor + * + * @param sampleRate in Hertz + * @param symbolRate symbols per second + */ + public DQPSKDemodulatorVector64(double sampleRate, int symbolRate) + { + super(sampleRate, symbolRate); + } + + @Override + public float[] demodulate(float[] i, float[] q) + { + int sampleLength = i.length; + int bufferOverlap = mBufferOverlap; + float mu = mMu; + + //Copy previous buffer residual samples to beginning of buffer. + System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap); + System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap); + + //Resize I/Q buffers if necessary + int requiredBufferLength = sampleLength + bufferOverlap; + if(mIBuffer.length != requiredBufferLength) + { + if(i.length % VECTOR_SPECIES.length() != 0) + { + throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length()); + } + mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength); + mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength); + } + + //Append new samples to the residual samples from the previous buffer. + System.arraycopy(i, 0, mIBuffer, bufferOverlap, sampleLength); + System.arraycopy(q, 0, mQBuffer, bufferOverlap, sampleLength); + //mIDecoded, mQDecoded and mPhase will be filled below during the decoding process. + + float[] interpolatedI = new float[VECTOR_SPECIES.length()]; + float[] interpolatedQ = new float[VECTOR_SPECIES.length()]; + float[] decodedPhases = new float[i.length]; + FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ; + + //Differential demodulation. + for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length()) + { + iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x); + qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate + + int offset = mInterpolationOffset + x; + int index; + for(int y = 0; y < VECTOR_SPECIES.length(); y++) + { + index = offset + y; + interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu); + interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu); + } + iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0); + qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0); + + //Multiply current complex sample by the complex conjugate of previous complex sample. + differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent)); + differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate)); + + //Calculate phase angles using Arc Tangent and export to the decoded phases array. + differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x); + } + + return decodedPhases; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java index 8c13b300..78efb2ff 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java @@ -1,47 +1,61 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 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.symbol; public enum Dibit { - D01_PLUS_3(false, true, 1, 4, 1), - D00_PLUS_1(false, false, 0, 0, 0), - D10_MINUS_1(true, false, 2, 8, 2), - D11_MINUS_3(true, true, 3, 12, 3); + D01_PLUS_3(false, true, 1, 4, 1, (float)(Math.PI / 4 * 3), (float)-Math.sin(Math.toRadians(45)), (float)Math.sin(Math.toRadians(45))), + D00_PLUS_1(false, false, 0, 0, 0, (float)(Math.PI / 4.0), (float)Math.sin(Math.toRadians(45)), (float)Math.sin(Math.toRadians(45))), + D10_MINUS_1(true, false, 2, 8, 2, (float)(Math.PI / -4.0), (float)Math.sin(Math.toRadians(45)), (float)-Math.sin(Math.toRadians(45))), + D11_MINUS_3(true, true, 3, 12, 3, (float)(Math.PI / 4 * -3), (float)-Math.sin(Math.toRadians(45)), (float)-Math.sin(Math.toRadians(45))); private boolean mBit1; private boolean mBit2; private int mLowValue; private int mHighValue; private int mValue; + private float mPhase; + private float mIdealI; + private float mIdealQ; - private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value) + /** + * Dibit constructor. + * @param bit1 boolean value + * @param bit2 boolean value + * @param lowValue integer value of the low order bit + * @param highValue integer value of the high order bit + * @param value integer value combined of both bits. + * @param phase optimal phase value for this symbol when transmitted. + * @param idealI ideal inphase value for this symbol when transmitted. + * @param idealQ ideal quadrature value for this symbol when transmitted. + */ + Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value, float phase, float idealI, float idealQ) { mBit1 = bit1; mBit2 = bit2; mLowValue = lowValue; mHighValue = highValue; mValue = value; + mPhase = phase; + mIdealI = idealI; + mIdealQ = idealQ; } /** @@ -52,6 +66,33 @@ public enum Dibit return mValue; } + /** + * Ideal phase angle when this symbol is transmitted. + * @return ideal phase angle in radians. + */ + public float getIdealPhase() + { + return mPhase; + } + + /** + * Ideal inphase value when this symbol is transmitted. + * @return ideal I + */ + public float getIdealI() + { + return mIdealI; + } + + /** + * Ideal quadrature value when this symbol is transmitted. + * @return ideal Q + */ + public float getIdealQ() + { + return mIdealQ; + } + /** * Allowable transition state 1 for trellis coded modulation. Indicates * the dibit that differs from this dibit in the LSB bit position. diff --git a/src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java b/src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java deleted file mode 100644 index c405a3c6..00000000 --- a/src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dsheirer.edac; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.bits.CorrectedBinaryMessage; - -/* - Adopted from https://github.com/boatbod/op25/blob/e5e8fe457240fb358a8163bcf6fe7dda5e4d8392/op25/gr-op25_repeater/lib/hamming.cc - */ -public class BPTC_17_12_3 { - - public static boolean decode17123(BinaryMessage d, int offset) - { - if(d==null) { - return false; - } - - // Calculate the checksum this column should have - boolean c0 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(6 + offset) ^ d.get(7 + offset) ^ d.get(9 + offset); - boolean c1 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(4 + offset) ^ d.get(7 + offset) ^ d.get(8 + offset) ^ d.get(10 + offset); - boolean c2 = d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(4 + offset) ^ d.get(5 + offset) ^ d.get(8 + offset) ^ d.get(9 + offset) ^ d.get(11 + offset); - boolean c3 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(4 + offset) ^ d.get(5 + offset) ^ d.get(7 + offset) ^ d.get(10 + offset); - boolean c4 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(5 + offset) ^ d.get(6 + offset) ^ d.get(8 + offset) ^ d.get(11 + offset); - - // Compare these with the actual bits - byte n = 0x00; - n |= (c0 != d.get(12 + offset)) ? 0x01 : 0x00; - n |= (c1 != d.get(13 + offset)) ? 0x02 : 0x00; - n |= (c2 != d.get(14 + offset)) ? 0x04 : 0x00; - n |= (c3 != d.get(15 + offset)) ? 0x08 : 0x00; - n |= (c4 != d.get(16 + offset)) ? 0x10 : 0x00; - - switch (n) { - // Parity bit errors - case 0x01: d.set(12 + offset, !d.get(12 + offset)); return true; - case 0x02: d.set(13 + offset, !d.get(13 + offset)); return true; - case 0x04: d.set(14 + offset, !d.get(14 + offset)); return true; - case 0x08: d.set(15 + offset, !d.get(15 + offset)); return true; - case 0x10: d.set(16 + offset, !d.get(16 + offset)); return true; - - // Data bit errors - case 0x1B: d.set(0 + offset, !d.get(0 + offset)); return true; - case 0x1F: d.set(1 + offset, !d.get(1 + offset)); return true; - case 0x17: d.set(2 + offset, !d.get(2 + offset)); return true; - case 0x07: d.set(3 + offset, !d.get(3 + offset)); return true; - case 0x0E: d.set(4 + offset, !d.get(4 + offset)); return true; - case 0x1C: d.set(5 + offset, !d.get(5 + offset)); return true; - case 0x11: d.set(6 + offset, !d.get(6 + offset)); return true; - case 0x0B: d.set(7 + offset, !d.get(7 + offset)); return true; - case 0x16: d.set(8 + offset, !d.get(8 + offset)); return true; - case 0x05: d.set(9 + offset, !d.get(9 + offset)); return true; - case 0x0A: d.set(10 + offset,!d.get(10 + offset)); return true; - case 0x14: d.set(11 + offset,!d.get(11 + offset)); return true; - - // No bit errors - case 0x00: return true; - - // Unrecoverable errors - default: return false; - } - } - -} diff --git a/src/main/java/io/github/dsheirer/edac/BPTC_196_96.java b/src/main/java/io/github/dsheirer/edac/BPTC_196_96.java deleted file mode 100644 index 0d5ac81b..00000000 --- a/src/main/java/io/github/dsheirer/edac/BPTC_196_96.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.edac; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.bits.BitSetFullException; -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Block Product Turbo Code (BPTC) 196/96 Support - */ -public class BPTC_196_96 -{ - public static final int BPTC_LENGTH = 196; - public static final int EXTRACTED_LENGTH = 96; //However, we set the 3x reserved bits in 96, 97, and 98 making the length 99 - public static final int MAX_ORIGINAL_INDEX = 136; - public static final int COLUMN_COUNT = 15; - public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit - public static final int CHECKSUM_COLUMN_COUNT = 4; - public static final int MESSAGE_START_INDEX = 4; - - /** - * DMR De-interleaving indices - */ - public static int[] BPTC_DEINTERLEAVE = new int[]{0, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 16, 1, 182, - 167, 152, 137, 122, 107, 92, 77, 62, 47, 32, 17, 2, 183, 168, 153, 138, 123, 108, 93, 78, 63, 48, 33, 18, 3, - 184, 169, 154, 139, 124, 109, 94, 79, 64, 49, 34, 19, 4, 185, 170, 155, 140, 125, 110, 95, 80, 65, 50, 35, 20, - 5, 186, 171, 156, 141, 126, 111, 96, 81, 66, 51, 36, 21, 6, 187, 172, 157, 142, 127, 112, 97, 82, 67, 52, 37, - 22, 7, 188, 173, 158, 143, 128, 113, 98, 83, 68, 53, 38, 23, 8, 189, 174, 159, 144, 129, 114, 99, 84, 69, 54, - 39, 24, 9, 190, 175, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 191, 176, 161, 146, 131, 116, 101, 86, 71, - 56, 41, 26, 11, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 193, 178, 163, 148, 133, 118, 103, - 88, 73, 58, 43, 28, 13, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 195, 180, 165, 150, 135, 120, - 105, 90, 75, 60, 45, 30, 15}; - - /** - * DMR Column Indices - */ - public static int[][] COLUMN_INDEXES = new int[][]{ - {1, 16, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166, 181}, - {2, 17, 32, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182}, - {3, 18, 33, 48, 63, 78, 93, 108, 123, 138, 153, 168, 183}, - {4, 19, 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184}, - {5, 20, 35, 50, 65, 80, 95, 110, 125, 140, 155, 170, 185}, - {6, 21, 36, 51, 66, 81, 96, 111, 126, 141, 156, 171, 186}, - {7, 22, 37, 52, 67, 82, 97, 112, 127, 142, 157, 172, 187}, - {8, 23, 38, 53, 68, 83, 98, 113, 128, 143, 158, 173, 188}, - {9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 159, 174, 189}, - {10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 175, 190}, - {11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 191}, - {12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192}, - {13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193}, - {14, 29, 44, 59, 74, 89, 104, 119, 134, 149, 164, 179, 194}, - {15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195}}; - - /** - * Performs de-interleave, error detect and correct and extracts a 96-bit message from the DMR BPTC(196,96) - * protected message - * @param original uncorrected BPTC(196,96) message - * @return error corrected and extracted 96-bit message - */ - public static CorrectedBinaryMessage extract(CorrectedBinaryMessage original) - { - //De-Interleave - CorrectedBinaryMessage message = deinterleave(original); - - //Perform error detection and correction - correct(message); - - //Extract the 96-bit message from the corrected 196 bits - CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(EXTRACTED_LENGTH); - - //Transfer the corrected bit count - extracted.setCorrectedBitCount(message.getCorrectedBitCount()); - - int index = MESSAGE_START_INDEX; - - while(index < MAX_ORIGINAL_INDEX) - { - if((index % COLUMN_COUNT) < MESSAGE_COLUMN_COUNT) - { - try - { - extracted.add(message.get(index)); - } - catch(BitSetFullException bsfe) - { - //Should never get to here - bsfe.printStackTrace(); - return extracted; - } - - index++; - } - else - { - index += CHECKSUM_COLUMN_COUNT; - } - } - - //Transfer bits R2, R1, and R0 to the end - RAS bits - extracted.set(96, message.get(0)); - extracted.set(97, message.get(1)); - extracted.set(98, message.get(2)); - - return extracted; - } - - /** - * De-interleaves the transmitted bits of a DMR BPTC(196,96) protected message - * @param message that contains interleaved (transmitted) message - * @return de-interleaved message - */ - private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage message) - { - CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BPTC_LENGTH); - for(int x = 0; x < BPTC_DEINTERLEAVE.length; x++) - { - deinterleaved.set(x, message.get(BPTC_DEINTERLEAVE[x])); - } - - return deinterleaved; - } - - /** - * Identifies the error indices from the row level Hamming(15,11,3) protected words - * @param message containing protected words in rows - * @return list of zero or more error indices - */ - private static List getRowErrors(CorrectedBinaryMessage message) - { - List rowErrors = new ArrayList<>(); - - int offset = 0; - int index = -1; - - for(int row = 0; row < 13; row++) - { - //The first bit of the message is a pad/reserved bit and is ignored. - offset = (row * 15) + 1; - index = Hamming15.getErrorIndex(message, offset); - - if(index >= 0) - { - rowErrors.add(index); - } - } - - //Note: row errors should already be in order and don't need sorted - return rowErrors; - } - - /** - * Identifies the error indices from the column level Hamming(13,9,3) protected words - * @param message containing protected words in columns - * @return list of zero or more error indices - */ - private static List getColumnErrors(CorrectedBinaryMessage message) - { - List columnErrors = new ArrayList<>(); - - int index = -1; - - for(int column = 0; column < 15; column++) - { - index = Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]); - - if(index >= 0) - { - columnErrors.add(index); - } - } - - Collections.sort(columnErrors); - return columnErrors; - } - - /** - * Performs error detection and correction on the DMR BPTC(196,96) protected message - * - * @param message to process and extract - */ - private static void correct(CorrectedBinaryMessage message) - { - List rowErrors = getRowErrors(message); - - if(!rowErrors.isEmpty()) - { - List columnErrors = getColumnErrors(message); - - if(matches(rowErrors, columnErrors)) - { - for(Integer index: rowErrors) - { - message.flip(index); - } - - message.setCorrectedBitCount(rowErrors.size()); - } - else - { - //Step 1: correct row errors and recalculate column errors (should be zero) - for(Integer index: rowErrors) - { - message.flip(index); - } - - List columnErrorsPart2 = getColumnErrors(message); - - if(columnErrorsPart2.isEmpty()) - { - //Success - message.setCorrectedBitCount(rowErrors.size()); - } - else - { - //Reverse the row error correction - for(Integer index: rowErrors) - { - message.flip(index); - } - - //Step 2: correct column errors and recalculate row errors (should be zero) - for(Integer index: columnErrors) - { - message.flip(index); - } - - List rowErrorsPart2 = getRowErrors(message); - - if(rowErrorsPart2.isEmpty()) - { - //Success - message.setCorrectedBitCount(columnErrors.size()); - } - else - { - //Reverse the column error correction - for(Integer index: columnErrors) - { - message.flip(index); - } - - //Set the error count greater than the maximum correctable column errors (16) - message.setCorrectedBitCount(16); - } - } - } - } - } - - /** - * Indicates if the errors detected by the row protected words match the errors detected by the column protected words - * @param rowErrors bit positions - * @param columnErrors bit positions - * @return true if they have the same quantity of bit position errors and the bit positions match - */ - private static boolean matches(List rowErrors, List columnErrors) - { - if(rowErrors.size() == columnErrors.size()) - { - for(int x = 0; x < rowErrors.size(); x++) - { - if(!rowErrors.get(x).equals(columnErrors.get(0))) - { - return false; - } - } - - return true; - } - else - { - return false; - } - } - - public static void main(String[] args) - { - String raw = "0000000001100010100101100011100100110100110101000100000011101001110001001000100001001010010100001000011111011000000011110000111000111111100011000011100001100000000101001010000100010111010100111001"; - System.out.println("RAW:" + raw); - - CorrectedBinaryMessage message = new CorrectedBinaryMessage(BinaryMessage.load(raw)); - System.out.println("LOD:" + message.toString()); - CorrectedBinaryMessage extracted = extract(message); - System.out.println("EXT:" + extracted.toString()); - } -} diff --git a/src/main/java/io/github/dsheirer/edac/Golay24.java b/src/main/java/io/github/dsheirer/edac/Golay24.java index b6735d96..0453a591 100644 --- a/src/main/java/io/github/dsheirer/edac/Golay24.java +++ b/src/main/java/io/github/dsheirer/edac/Golay24.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/io/github/dsheirer/edac/Hamming13.java b/src/main/java/io/github/dsheirer/edac/Hamming13.java index bb2a1f78..c0b4e1a6 100644 --- a/src/main/java/io/github/dsheirer/edac/Hamming13.java +++ b/src/main/java/io/github/dsheirer/edac/Hamming13.java @@ -1,6 +1,26 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.edac; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; /** * Implements Hamming(13,9,3) Error Detection algorithm @@ -8,8 +28,8 @@ import io.github.dsheirer.bits.BinaryMessage; public class Hamming13 { //DMR Checksums from generator matrix TS 102 361-1 Table B.14 - private static int[] CHECKSUMS = new int[]{0xF, 0xE, 0x7, 0xA, 0x5, 0XB, 0xC, 0x6, 0x3}; - private static int[] ERROR_INDEX = new int[]{-1, 12, 11, 8, 10, 4, 7, 2, 9, -1, 3, 5, 6, -1, 1, 0, -1}; + private static int[] CHECKSUMS = new int[]{0xF, 0xE, 0x7, 0xA, 0x5, 0xB, 0xC, 0x6, 0x3, 0x8, 0x4, 0x2, 0x1}; + private static int[] ERROR_INDEX = new int[]{-1, 12, 11, 8, 10, 4, 7, 2, 9, -1, 3, 5, 6, -1, 1, 0, -1, -1}; /** * Calculates the bit error index of the Hamming(13,9,3) protected word that is contained in the binary message @@ -28,7 +48,7 @@ public class Hamming13 return offset + ERROR_INDEX[syndrome]; } - return -1; + return IHamming.NO_ERRORS; } /** @@ -53,11 +73,11 @@ public class Hamming13 } else { - return 10000 + errorIndex; + return 1000; } } - return -1; + return IHamming.NO_ERRORS; } /** @@ -95,7 +115,7 @@ public class Hamming13 return calculated; } - private static int getSyndrome(BinaryMessage message, int[] indices) + public static int getSyndrome(BinaryMessage message, int[] indices) { int calculated = calculateChecksum(message, indices); @@ -126,4 +146,16 @@ public class Hamming13 int checksum = message.getInt(offset + 9, offset + 12); return (checksum ^ calculated); } + + public static void main(String[] args) + { + String transmitted = "0000010000001"; + String correct = "0000000000000"; + String recommended = "0001000000001"; +// CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.load(transmitted)); + CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.load(correct)); +// CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.load(recommended)); + int syndrome = getSyndrome(cbm, 0); + System.out.println("Syndrome " + syndrome); + } } diff --git a/src/main/java/io/github/dsheirer/edac/Hamming15.java b/src/main/java/io/github/dsheirer/edac/Hamming15.java index f498bb72..e11e9acc 100644 --- a/src/main/java/io/github/dsheirer/edac/Hamming15.java +++ b/src/main/java/io/github/dsheirer/edac/Hamming15.java @@ -1,3 +1,22 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.edac; import io.github.dsheirer.bits.BinaryMessage; @@ -8,7 +27,7 @@ import io.github.dsheirer.bits.BinaryMessage; public class Hamming15 { //DMR Checksums from generator matrix TS 102 361-1 Table B.15 - private static int[] CHECKSUMS = new int[]{0x9, 0xD, 0xF, 0xE, 0x7, 0XA, 0x5, 0xB, 0xC, 0x6, 0x3}; + private static int[] CHECKSUMS = new int[]{0x9, 0xD, 0xF, 0xE, 0x7, 0xA, 0x5, 0xB, 0xC, 0x6, 0x3, 0x8, 0x4, 0x2, 0x1}; private static int[] ERROR_INDEX = new int[]{-1, 14, 13, 10, 12, 6, 9, 4, 11, 0, 5, 7, 8, 1, 3, 2}; /** @@ -28,7 +47,7 @@ public class Hamming15 return offset + ERROR_INDEX[syndrome]; } - return -1; + return IHamming.NO_ERRORS; } /** @@ -58,7 +77,7 @@ public class Hamming15 * @param offset to bit 0 of the hamming protected word * @return syndrome that can be used with the ERROR_INDEX error to find the index of the bit position error */ - private static int getSyndrome(BinaryMessage message, int offset) + public static int getSyndrome(BinaryMessage message, int offset) { int calculated = calculateChecksum(message, offset); int checksum = message.getInt(offset + 11, offset + 14); diff --git a/src/main/java/io/github/dsheirer/edac/Hamming16.java b/src/main/java/io/github/dsheirer/edac/Hamming16.java index 951c4539..7436fb5c 100644 --- a/src/main/java/io/github/dsheirer/edac/Hamming16.java +++ b/src/main/java/io/github/dsheirer/edac/Hamming16.java @@ -1,13 +1,63 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.edac; import io.github.dsheirer.bits.BinaryMessage; /** - * Hamming(16,11,5) Error detection and correction utility. + * Hamming(16,11,4) Error detection and correction utility. This code is capable of correcting single-bit errors and + * detecting (but not correcting) double-bit errors. */ -public class Hamming16 +public class Hamming16 implements IHamming { - private static int[] CHECKSUMS = new int[]{0x13, 0x1A, 0x1F, 0x1C, 0x0E, 0x15, 0x0B, 0x16, 0x19, 0x0D, 0x07}; + private static int[] CHECKSUMS = new int[]{0x13, 0x1A, 0x1F, 0x1C, 0x0E, 0x15, 0x0B, 0x16, 0x19, 0x0D, 0x07, 0x10, + 0x08, 0x04, 0x02, 0x01}; + + /** + * Calculates the bit error index of the Hamming(16,11,4) protected word that is contained in the binary message + * starting at the specified offset. + * + * @param message containing a Hamming protected word + * @param offset to the start of the protected word + * @return message index for an error bit or -1 if no errors are detected or 1000 if two bit errors are detected. + */ + public int getErrorIndex(BinaryMessage message, int offset) + { + int syndrome = getSyndrome(message, offset); + + if(syndrome == 0) + { + return NO_ERRORS; + } + + for(int index = 0; index < CHECKSUMS.length; index++) + { + if(syndrome == CHECKSUMS[index]) + { + return index + offset; + } + } + + return MULTIPLE_ERRORS; + } + /** * Performs error detection and correction of any single-bit errors and detection of any double-bit errors (SECDED) @@ -22,78 +72,36 @@ public class Hamming16 { int syndrome = getSyndrome(frame, startIndex); - switch(syndrome) + if (syndrome == 0) { - case 0: - return 0; - case 1: - frame.flip(startIndex + 15); //Parity 1 - return 1; - case 2: - frame.flip(startIndex + 14); //Parity 2 - return 1; - case 3: - frame.flip(startIndex + 10); //Data 1 - return 1; - case 4: - frame.flip(startIndex + 13); //Parity 4 - return 1; - case 5: - frame.flip(startIndex + 9); //Data 2 - return 1; - case 6: - frame.flip(startIndex + 8); //Data 3 - return 1; - case 7: - frame.flip(startIndex + 7); //Data 4 - return 1; - case 8: - frame.flip(startIndex + 12); //Parity 8 - return 1; - case 9: - frame.flip(startIndex + 6); //Data 5 - return 1; - case 10: - frame.flip(startIndex + 5); //Data 6 - return 1; - case 11: - frame.flip(startIndex + 4); //Data 7 - return 1; - case 12: - frame.flip(startIndex + 3); //Data 8 - return 1; - case 13: - frame.flip(startIndex + 2); //Data 9 - return 1; - case 14: - frame.flip(startIndex + 1); //Data 10 - return 1; - case 15: - frame.flip(startIndex); //Data 11 - return 1; - case 16: - frame.flip(startIndex + 11); //Parity 16 - return 1; - default: - return 2; + return 0; } + + for(int index = 0; index < CHECKSUMS.length; index++) + { + if(CHECKSUMS[index] == syndrome) + { + frame.flip(startIndex + index); + return 1; + } + } + + return 2; } /** - * Calculates the checksum (Parity 16,8,4,2,1) for data (11 <> 1 ) bits. + * Calculates the checksum from the transmitted data bits. * * @param frame - frame containing hamming protected word * @param startIndex - start bit index of the hamming protected word - * @return parity value, 0 - 16 + * @return calculated checksum value */ private static int calculateChecksum(BinaryMessage frame, int startIndex) { int calculated = 0; //Starting value /* Iterate the set bits and XOR running checksum with lookup value */ - for(int i = frame.nextSetBit(startIndex); - i >= startIndex && i < startIndex + 11; - i = frame.nextSetBit(i + 1)) + for(int i = frame.nextSetBit(startIndex); i >= startIndex && i < startIndex + 11; i = frame.nextSetBit(i + 1)) { calculated ^= CHECKSUMS[i - startIndex]; } @@ -109,12 +117,10 @@ public class Hamming16 * @param startIndex - of bit 0 of the hamming protected word * @return - 0 (no errors) or 1 (single bit error corrected) */ - private static int getSyndrome(BinaryMessage frame, int startIndex) + public static int getSyndrome(BinaryMessage frame, int startIndex) { int calculated = calculateChecksum(frame, startIndex); - int checksum = frame.getInt(startIndex + 11, startIndex + 15); - return (checksum ^ calculated); } } diff --git a/src/main/java/io/github/dsheirer/edac/Hamming17.java b/src/main/java/io/github/dsheirer/edac/Hamming17.java index 86b17997..3c94ff85 100644 --- a/src/main/java/io/github/dsheirer/edac/Hamming17.java +++ b/src/main/java/io/github/dsheirer/edac/Hamming17.java @@ -1,3 +1,22 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.edac; import io.github.dsheirer.bits.BinaryMessage; @@ -5,81 +24,37 @@ import io.github.dsheirer.bits.BinaryMessage; /** * Hamming(17,12,3) Error detection and correction utility. */ -public class Hamming17 +public class Hamming17 implements IHamming { - private static int[] CHECKSUMS = new int[]{0x1B, 0x1F, 0x1D, 0x1C, 0x0E, 0x07, 0x11, 0x1A, 0x0D, 0x14, 0x0A, 0x05}; + private static int[] CHECKSUMS = new int[]{0x1B, 0x1F, 0x1D, 0x1C, 0x0E, 0x07, 0x11, 0x1A, 0x0D, 0x14, 0x0A, 0x05, + 0x10, 0x08, 0x04, 0x02, 0x01}; /** - * Performs error detection and correction of any single-bit errors and detection of any double-bit errors (SECDED) + * Calculates the bit error index of the Hamming(17,12,3) protected word that is contained in the binary message + * starting at the specified offset. * - * @param frame - binary frame containing a hamming(17,12,5) protected field - * @param startIndex - offset to the first bit of the field - * @return - 0 = no errors - * 1 = a single-bit error was detected and corrected - * 2 = two or more errors detected - no corrections made + * @param message containing a Hamming protected word + * @param offset to the start of the protected word + * @return message index for an error bit or -1 if no errors are detected or 1000 if more than one bit error detected. */ - public static int checkAndCorrect(BinaryMessage frame, int startIndex) + public int getErrorIndex(BinaryMessage message, int offset) { - int syndrome = getSyndrome(frame, startIndex); + int syndrome = getSyndrome(message, offset); - switch(syndrome) + if(syndrome == 0) { - case 0: - return 0; - case 1: - frame.flip(startIndex + 16); //Parity 1 - return 1; - case 2: - frame.flip(startIndex + 15); //Parity 2 - return 1; - case 3: - frame.flip(startIndex + 11); //Data 1 - return 1; - case 4: - frame.flip(startIndex + 14); //Parity 4 - return 1; - case 5: - frame.flip(startIndex + 10); //Data 2 - return 1; - case 6: - frame.flip(startIndex + 9); //Data 3 - return 1; - case 7: - frame.flip(startIndex + 8); //Data 4 - return 1; - case 8: - frame.flip(startIndex + 13); //Parity 8 - return 1; - case 9: - frame.flip(startIndex + 7); //Data 5 - return 1; - case 10: - frame.flip(startIndex + 6); //Data 6 - return 1; - case 11: - frame.flip(startIndex + 5); //Data 7 - return 1; - case 12: - frame.flip(startIndex + 4); //Data 8 - return 1; - case 13: - frame.flip(startIndex + 3); //Data 9 - return 1; - case 14: - frame.flip(startIndex + 2); //Data 10 - return 1; - case 15: - frame.flip(startIndex + 1); //Data 11 - return 1; - case 16: - frame.flip(startIndex + 12); //Parity 16 - return 1; - case 17: - frame.flip(startIndex); //Data 12 - return 1; - default: - return 2; + return NO_ERRORS; } + + for(int index = 0; index < CHECKSUMS.length; index++) + { + if(syndrome == CHECKSUMS[index]) + { + return index + offset; + } + } + + return MULTIPLE_ERRORS; } /** @@ -87,16 +62,14 @@ public class Hamming17 * * @param frame - frame containing hamming protected word * @param startIndex - start bit index of the hamming protected word - * @return parity value, 0 - 16 + * @return checksum value */ private static int calculateChecksum(BinaryMessage frame, int startIndex) { int calculated = 0; //Starting value /* Iterate the set bits and XOR running checksum with lookup value */ - for(int i = frame.nextSetBit(startIndex); - i >= startIndex && i < startIndex + 12; - i = frame.nextSetBit(i + 1)) + for(int i = frame.nextSetBit(startIndex); i >= startIndex && i < startIndex + 12; i = frame.nextSetBit(i + 1)) { calculated ^= CHECKSUMS[i - startIndex]; } @@ -112,12 +85,10 @@ public class Hamming17 * @param startIndex - of bit 0 of the hamming protected word * @return - 0 (no errors) or 1 (single bit error corrected) */ - private static int getSyndrome(BinaryMessage frame, int startIndex) + public static int getSyndrome(BinaryMessage frame, int startIndex) { int calculated = calculateChecksum(frame, startIndex); - int checksum = frame.getInt(startIndex + 12, startIndex + 16); - return (checksum ^ calculated); } } diff --git a/src/main/java/io/github/dsheirer/edac/IHamming.java b/src/main/java/io/github/dsheirer/edac/IHamming.java new file mode 100644 index 00000000..199cc3e2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/IHamming.java @@ -0,0 +1,39 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.edac; + +import io.github.dsheirer.bits.BinaryMessage; + +/** + * Interface for Hamming error index calculator. + */ +public interface IHamming +{ + int NO_ERRORS = -1; + int MULTIPLE_ERRORS = 1000; + + /** + * Calculate the error index for the Hamming protected binary sequence + * @param message containing the binary sequence + * @param offset to the start of the binary sequence + * @return -1 if no errors, single-bit error index, or 1000 if there is more than a single-bit error detected. + */ + int getErrorIndex(BinaryMessage message, int offset); +} 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 deleted file mode 100644 index 6e704215..00000000 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java +++ /dev/null @@ -1,200 +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.gui.instrument.decoder; - -import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart; -import io.github.dsheirer.gui.instrument.chart.DoubleLineChart; -import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart; -import io.github.dsheirer.gui.instrument.chart.PhaseLineChart; -import io.github.dsheirer.gui.instrument.chart.SymbolChart; -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.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); - - private HBox mSampleChartBox; - private ComplexSampleLineChart mSampleLineChart; - private EyeDiagramChart mEyeDiagramChart; - private HBox mDecoderChartBox; - private SymbolChart mSymbolChart; - private PhaseLineChart mPLLPhaseErrorLineChart; - private DoubleLineChart mPLLFrequencyLineChart; - private DoubleLineChart mSamplesPerSymbolLineChart; - private Broadcaster mFilteredBufferBroadcaster = new Broadcaster(); - private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR()); - - public DMRPane() - { - super(DecoderType.P25_PHASE1); - init(); - } - - private void init() - { - addListener(nativeBuffer -> - { - Iterator iterator = nativeBuffer.iterator(); - - while(iterator.hasNext()) - { - getDecoder().receive(iterator.next()); - } - }); - - getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster); - getDecoder().setComplexSymbolListener(getSymbolChart()); - getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart()); - getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart()); - getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart()); - getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart()); - mFilteredBufferBroadcaster.addListener(getSampleLineChart()); - - HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS); - HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS); - getChildren().addAll(getSampleChartBox(), getDecoderChartBox()); - - mDecoder.setMessageListener(message -> mLog.debug(message.toString())); - - } - - @Override - public void setSampleRate(double sampleRate) - { - mLog.debug("Configuring for sample rate: " + sampleRate); - - mDecoder.setSampleRate(sampleRate); - double samplesPerSymbol = sampleRate / 4800.0; - - getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol); - } - - private DMRDecoderInstrumented getDecoder() - { - return mDecoder; - } - - private SymbolChart getSymbolChart() - { - if(mSymbolChart == null) - { - mSymbolChart = new SymbolChart(10); - } - - return mSymbolChart; - } - - private HBox getDecoderChartBox() - { - if(mDecoderChartBox == null) - { - mDecoderChartBox = new HBox(); - mDecoderChartBox.setMaxHeight(Double.MAX_VALUE); - getSymbolChart().setMaxWidth(Double.MAX_VALUE); - getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE); - getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE); - HBox.setHgrow(getSymbolChart(), Priority.ALWAYS); - HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS); - HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS); - mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(), - getPLLFrequencyLineChart()); - } - - return mDecoderChartBox; - } - - private HBox getSampleChartBox() - { - if(mSampleChartBox == null) - { - mSampleChartBox = new HBox(); - mSampleChartBox.setMaxHeight(Double.MAX_VALUE); - getSampleLineChart().setMaxWidth(Double.MAX_VALUE); - getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE); - getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE); - HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS); - HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS); - HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS); - mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(), - getSamplesPerSymbolLineChart()); - } - - return mSampleChartBox; - } - - private ComplexSampleLineChart getSampleLineChart() - { - if(mSampleLineChart == null) - { - mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10); - } - - return mSampleLineChart; - } - - private EyeDiagramChart getEyeDiagramChart() - { - if(mEyeDiagramChart == null) - { - mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4"); - } - - return mEyeDiagramChart; - } - - private PhaseLineChart getPLLPhaseErrorLineChart() - { - if(mPLLPhaseErrorLineChart == null) - { - mPLLPhaseErrorLineChart = new PhaseLineChart(40); - } - - return mPLLPhaseErrorLineChart; - } - - private DoubleLineChart getPLLFrequencyLineChart() - { - if(mPLLFrequencyLineChart == null) - { - mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40); - } - - return mPLLFrequencyLineChart; - } - - private DoubleLineChart getSamplesPerSymbolLineChart() - { - if(mSamplesPerSymbolLineChart == null) - { - mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40); - } - - return mSamplesPerSymbolLineChart; - } -} diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java index b9656872..151b8de7 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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.gui.instrument.decoder; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder; - import java.util.EnumSet; public class DecoderPaneFactory @@ -33,8 +32,7 @@ public class DecoderPaneFactory DecoderType.LTR_NET, DecoderType.MDC1200, DecoderType.MPT1327, - DecoderType.TAIT_1200, - DecoderType.DMR); + DecoderType.TAIT_1200); /** * Creates a decoder pane for the decoder type @@ -59,8 +57,6 @@ public class DecoderPaneFactory throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type"); case P25_PHASE2: return new P25Phase2HDQPSKPane(); - case DMR: - return new DMRPane(); } return getDefaultPane(); diff --git a/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java index bfc49965..a3646217 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java @@ -20,6 +20,7 @@ package io.github.dsheirer.gui.viewer; import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.dmr.DMRHardSymbolProcessor; import io.github.dsheirer.module.decode.dmr.DMRMessageFramer; import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor; import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; @@ -168,7 +169,9 @@ public class DmrViewer extends VBox public void run() { List messages = new ArrayList<>(); - DMRMessageFramer messageFramer = new DMRMessageFramer(null); + DMRMessageFramer messageFramer = new DMRMessageFramer(); + messageFramer.start(); + DMRHardSymbolProcessor symbolProcessor = new DMRHardSymbolProcessor(messageFramer); DecodeConfigDMR config = new DecodeConfigDMR(); config.setUseCompressedTalkgroups(useCompressed); DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config); @@ -180,7 +183,7 @@ public class DmrViewer extends VBox while(reader.hasNext()) { ByteBuffer buffer = reader.next(); - messageFramer.receive(buffer); + symbolProcessor.receive(buffer); } } catch(Exception ioe) diff --git a/src/main/java/io/github/dsheirer/message/EmptyTimeslotPlaceholderMessage.java b/src/main/java/io/github/dsheirer/message/EmptyTimeslotPlaceholderMessage.java new file mode 100644 index 00000000..23e7909b --- /dev/null +++ b/src/main/java/io/github/dsheirer/message/EmptyTimeslotPlaceholderMessage.java @@ -0,0 +1,102 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.message; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.protocol.Protocol; +import java.util.Collections; +import java.util.List; + +/** + * Empty message to convey that a number of bits were processed for an empty timeslot. + */ +public class EmptyTimeslotPlaceholderMessage extends Message +{ + private int mBitsProcessed; + private Protocol mProtocol; + private int mTimeslot = 0; + + /** + * Constructs a sync loss message. + * + * @param timestamp of the message + * @param bitsProcessed without a sync pattern detection of message decode + * @param timeslot that lost the sync + */ + public EmptyTimeslotPlaceholderMessage(long timestamp, int bitsProcessed, Protocol protocol, int timeslot) + { + super(timestamp); + mBitsProcessed = bitsProcessed; + mProtocol = protocol; + mTimeslot = timeslot; + } + + /** + * Constructs a sync loss message. + * + * @param timestamp of the message + * @param bitsProcessed without a sync pattern detection of message decode + */ + public EmptyTimeslotPlaceholderMessage(long timestamp, int bitsProcessed, Protocol protocol) + { + this(timestamp, bitsProcessed, protocol, 0); + } + + /** + * Timeslot that lost the sync + * @return timeslot or a default of 0 + */ + @Override + public int getTimeslot() + { + return mTimeslot; + } + + /** + * Indicates the number of bits that were processed without a sync detection or message decode + */ + public int getBitsProcessed() + { + return mBitsProcessed; + } + + @Override + public String toString() + { + return "EMPTY TIMESLOT - BITS PROCESSED [" + getBitsProcessed() + "]"; + } + + @Override + public boolean isValid() + { + return true; + } + + @Override + public Protocol getProtocol() + { + return mProtocol; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/message/TimeslotMessage.java b/src/main/java/io/github/dsheirer/message/TimeslotMessage.java index 8da4091d..64a93f2b 100644 --- a/src/main/java/io/github/dsheirer/message/TimeslotMessage.java +++ b/src/main/java/io/github/dsheirer/message/TimeslotMessage.java @@ -24,7 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; /** * Timeslot message base class. */ -public abstract class TimeslotMessage extends AbstractMessage +public abstract class TimeslotMessage extends AbstractMessage implements IMessage { public static final int TIMESLOT_0 = 0; public static final int TIMESLOT_1 = 1; diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java index 89a113b6..635ef6b9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -479,7 +479,7 @@ public class DecoderFactory AliasList aliasList, DecodeConfigDMR decodeConfig, TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor) { - modules.add(new DMRDecoder(decodeConfig)); + modules.add(new DMRDecoder(decodeConfig, channel.isTrafficChannel())); DMRTrafficChannelManager dmrTrafficChannelManager = null; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java deleted file mode 100644 index bd7e516c..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java +++ /dev/null @@ -1,550 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.module.decode.dmr; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; -import io.github.dsheirer.dsp.symbol.Dibit; -import io.github.dsheirer.dsp.symbol.DibitDelayBuffer; -import io.github.dsheirer.dsp.symbol.QPSKCarrierLock; -import io.github.dsheirer.module.decode.dmr.message.CACH; -import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Processes a stream of Dibit symbols and performs burst detection and timeslot framing. This framer - * also detects abnormal PLL phase locks and issues PLL phase lock corrections. - * - * Timeslot Map: - * 0: Common Association Channel (CACH) and Slow Link Control (SLC) - * 1. Timeslot 1 - * 2. Timeslot 2 - */ -public class DMRBurstFramer implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(DMRBurstFramer.class); - - private static final int DMR_SYMBOL_RATE = 4800; - private static final double PLL_PHASE_CORRECTION_90_DEGREES = (double)DMR_SYMBOL_RATE / 4.0; - private static final double PLL_PHASE_CORRECTION_180_DEGREES = (double)DMR_SYMBOL_RATE / 2.0; - - private static final int BURST_DIBIT_START_TS1 = 0; - private static final int BURST_DIBIT_START_TS2 = 144; - private static final int BURST_DIBIT_LENGTH = 144; - private static final int TWO_TIMESLOT_DIBIT_LENGTH = BURST_DIBIT_LENGTH * 2; - private static final int SYNC_DIBIT_OFFSET_TS1 = 66; - private static final int SYNC_DIBIT_OFFSET_TS2 = 210; - private static final int SYNC_DIBIT_LENGTH = 24; - private static final int MAX_STREAMING_SYNC_DETECT_BIT_ERRORS = 4; - private static final int MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS = 6; - - /** - * Threshold for issuing a sync loss message. This is set to trigger once the dibit count exceeds one second of - * dibits (4800 baud/dibits) plus two bursts dibit length. The two burst length padding is so that we don't reset - * the dibit counter below the burst sync check threshold. - */ - private static final int SYNC_LOSS_MESSAGE_THRESHOLD = DMR_SYMBOL_RATE + TWO_TIMESLOT_DIBIT_LENGTH; - - /** - * The message buffer is sized to hold two DMR bursts of 144 dibits (288 bits) each. - */ - private DibitDelayBuffer mMessageBuffer = new DibitDelayBuffer(BURST_DIBIT_LENGTH * 2); - - /** - * The sync delay buffer is sized to align sync detection with two bursts being fully loaded into the message and - * timeslot 1 region of the message buffer is aligned with the sync delay buffer. - * - * This delay equals the number of dibits in the second half of the burst payload: 54 dibits (108 bits). - */ - private DibitDelayBuffer mSyncDelayBuffer = new DibitDelayBuffer(54); - - /** - * Synchronized indicates that either the primary or secondary sync tracker is currently synchronized to - * one of the timeslot bursts. Once either sync tracker obtains a valid sync pattern, this flag is set - * to true. The synchronized state will remain true as long as at least one of the two sync trackers - * maintains sync state. Once both sync trackers lose sync, the framer will fallback into sync search/detection - * mode to inspect each dibit until a sync match can be (re)discovered. - */ - private boolean mSynchronized = false; - - /** - * Tracks the number of dibits received to trigger burst or sync loss message processing - */ - private int mDibitCounter = 0; - - /** - * The Sync detection is used to gain initial lock to a sync pattern or to regain sync if lost. The sync detector - * is used to align a DMR burst into the timeslot 1 region of the message dibit delay buffer so that it can be - * inspected to ensure the timeslots are aligned. - */ - private DMRSyncDetector mSyncDetectorTimeslot1 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS, - MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS); - private DMRSyncDetector mSyncDetectorTimeslot2 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS, - MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS); - - /** - * Timeslot sync trackers track the synchronization state for each of the two timeslots. - */ - private SyncTracker mSyncTrackerTimeslot1 = new SyncTracker(); - private SyncTracker mSyncTrackerTimeslot2 = new SyncTracker(); - - /** - * Timeslot alignment tracker tracks that the timeslots in the delay buffer are aligned. - */ - private TimeslotAlignmentTracker mTimeslotAlignmentTracker = new TimeslotAlignmentTracker(); - - private IDMRBurstDetectListener mBurstDetectListener; - private IPhaseLockedLoop mPhaseLockedLoop; - - /** - * Constructs an instance - * @param listener to be notified of framed burst detections and/or sync loss bits processed - * @param phaseLockedLoop to receive symbol alignment corrections - */ - public DMRBurstFramer(IDMRBurstDetectListener listener, IPhaseLockedLoop phaseLockedLoop) - { - mBurstDetectListener = listener; - mPhaseLockedLoop = phaseLockedLoop; - } - - /** - * Primary dibit symbol input method - * @param dibit to process - */ - @Override - public void receive(Dibit dibit) - { - mDibitCounter++; - - //Feed the message buffer first to ensure buffer contains two full bursts when a sync is detected - mMessageBuffer.put(dibit); - - //Feed the sync delay buffer and reassign the delayed dibit to feed the sync detector if we're not synchronized - dibit = mSyncDelayBuffer.getAndPut(dibit); - - if(mSynchronized) - { - if(mDibitCounter >= TWO_TIMESLOT_DIBIT_LENGTH) - { - if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH) - { - processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0); - } - - dispatch(); - } - } - else - { - mSyncDetectorTimeslot2.add(dibit); - - if(mSyncDetectorTimeslot2.hasSync()) - { - dispatch(); - } - else if(mDibitCounter >= SYNC_LOSS_MESSAGE_THRESHOLD) - { - processSyncLossDibits(DMR_SYMBOL_RATE, 0); - } - } - } - - /** - * Dispatches burst 1 and/or burst 2 that is currently in the message buffer - */ - private void dispatch() - { - if(mBurstDetectListener != null) - { - if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH) - { - processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0); - } - - mSyncDetectorTimeslot1.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS1)); - mSyncDetectorTimeslot2.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS2)); - - mSyncTrackerTimeslot1.update(mSyncDetectorTimeslot1.getSyncPattern(), mSyncDetectorTimeslot1.getCarrierLock()); - mSyncTrackerTimeslot2.update(mSyncDetectorTimeslot2.getSyncPattern(), mSyncDetectorTimeslot2.getCarrierLock()); - - CorrectedBinaryMessage burst1 = null; - CorrectedBinaryMessage burst2 = null; - - CACH cach1 = null; - CACH cach2 = null; - - //If we have valid sync but PLL tracker misalignment, correct both timeslots before we extract messages - if((mSyncTrackerTimeslot1.hasSync() && mSyncTrackerTimeslot1.hasCarrierMisAlignment()) || - (mSyncTrackerTimeslot2.hasSync() && mSyncTrackerTimeslot2.hasCarrierMisAlignment())) - { -// mLog.debug("Repairing Message Bits - PLL Mis-Alignment Detected"); - if(mSyncTrackerTimeslot1.hasCarrierMisAlignment()) - { - repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot1.getCarrierLock()); - repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot1.getCarrierLock()); - } - else - { - repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot2.getCarrierLock()); - repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot2.getCarrierLock()); - } - } - - if(mSyncTrackerTimeslot1.hasSync()) - { - burst1 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS1, BURST_DIBIT_LENGTH); - burst1.incrementCorrectedBitCount(mSyncDetectorTimeslot1.getPatternMatchBitErrorCount()); - - if(mSyncTrackerTimeslot1.getSyncPattern().hasCACH()) - { - cach1 = CACH.getCACH(burst1); - } - } - - if(mSyncTrackerTimeslot2.hasSync()) - { - burst2 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS2, BURST_DIBIT_LENGTH); - burst2.incrementCorrectedBitCount(mSyncDetectorTimeslot2.getPatternMatchBitErrorCount()); - - if(mSyncTrackerTimeslot2.getSyncPattern().hasCACH()) - { - cach2 = CACH.getCACH(burst2); - } - } - - //Update the timeslot alignment tracker using the sync patterns and optional CACHes from each timeslot - mTimeslotAlignmentTracker.update(cach1, cach2, mSyncDetectorTimeslot1.getSyncPattern(), - mSyncDetectorTimeslot2.getSyncPattern()); - - if(mTimeslotAlignmentTracker.hasSufficientData()) - { - if(mTimeslotAlignmentTracker.isAligned()) - { - if(burst1 != null) - { - mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 1); - mDibitCounter -= BURST_DIBIT_LENGTH; - } - else - { - processSyncLossDibits(BURST_DIBIT_LENGTH, 1); - } - - if(burst2 != null) - { - mBurstDetectListener.burstDetected(burst2, mSyncTrackerTimeslot2.getSyncPattern(), 2); - mDibitCounter -= BURST_DIBIT_LENGTH; - } - else - { - processSyncLossDibits(BURST_DIBIT_LENGTH, 2); - } - - mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync(); - } - else - { - if(burst1 != null) - { - mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 2); - mDibitCounter -= BURST_DIBIT_LENGTH; - - //Transfer the sync pattern from tracker 1 to tracker 2 so we don't lose voice framing - mSyncTrackerTimeslot2.setSyncPattern(mSyncTrackerTimeslot1.getSyncPattern()); - } - else - { - processSyncLossDibits(BURST_DIBIT_LENGTH, 2); - mSyncTrackerTimeslot2.reset(); - } - - mTimeslotAlignmentTracker.reset(); - mSyncTrackerTimeslot1.reset(); - mDibitCounter = BURST_DIBIT_LENGTH; - mSynchronized = true; - } - } - else - { - //When we don't have sufficient timeslot tracking data, we either have misaligned timeslots from a - //repeater or we have some form of mobile burst. If mobile, process to the correct timeslto, otherwise - //dump the messages to timeslot 0 so that the burst doesn't corrupt the decoder states for either - // timeslot, until we positively regain timeslot tracking - if(burst1 != null) - { - DMRSyncPattern sync1 = mSyncTrackerTimeslot1.getSyncPattern(); - - if(sync1.isMobileStationSyncPattern() || sync1.isDirectModeTS1()) - { - mBurstDetectListener.burstDetected(burst1, sync1, 1); - } - else if(sync1.isDirectModeTS2()) - { - mBurstDetectListener.burstDetected(burst1, sync1, 2); - } - else - { - mBurstDetectListener.burstDetected(burst1, sync1, 0); - } - - mDibitCounter -= BURST_DIBIT_LENGTH; - } - else - { - processSyncLossDibits(BURST_DIBIT_LENGTH, 0); - } - - if(burst2 != null) - { - DMRSyncPattern sync2 = mSyncTrackerTimeslot2.getSyncPattern(); - - if(sync2.isMobileStationSyncPattern() || sync2.isDirectModeTS1()) - { - mBurstDetectListener.burstDetected(burst2, sync2, 1); - } - else if(sync2.isDirectModeTS2()) - { - mBurstDetectListener.burstDetected(burst2, sync2, 2); - } - else - { - mBurstDetectListener.burstDetected(burst2, sync2, 0); - } - - mDibitCounter -= BURST_DIBIT_LENGTH; - } - else - { - processSyncLossDibits(BURST_DIBIT_LENGTH, 0); - } - - mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync(); - } - - if(!mSynchronized) - { - mTimeslotAlignmentTracker.reset(); - mSyncTrackerTimeslot1.reset(); - mSyncTrackerTimeslot2.reset(); - } - - if(mDibitCounter < 0) - { - mDibitCounter = 0; - } - } - } - - /** - * Repairs the captured dibits in the message buffer when a PLL lock misalignment is detected. - * @param offset to start of burst - * @param carrierLock that indicates the mis-alignment - */ - private void repairPLLMisalignment(int offset, QPSKCarrierLock carrierLock) - { - int end = offset + BURST_DIBIT_LENGTH; - - for(int x = offset; x < end; x++) - { - Dibit misalignedDibit = mMessageBuffer.get(x); - mMessageBuffer.set(x, carrierLock.correct(misalignedDibit)); - } - - if(mPhaseLockedLoop != null) - { -// mLog.warn("Repairing PLL Phase Lock Mis-Alignment - Detected: " + carrierLock); - switch(carrierLock) - { - case PLUS_90: - mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_90_DEGREES); - break; - case MINUS_90: - mPhaseLockedLoop.correctInversion(-PLL_PHASE_CORRECTION_90_DEGREES); - break; - case INVERTED: - mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_180_DEGREES); - break; - } - } - } - - /** - * Resets the message and sync delay buffers and resets the dibit counter to zero. - */ - public void reset() - { - mDibitCounter = 0; - mMessageBuffer.reset(); - mSyncDelayBuffer.reset(); - mTimeslotAlignmentTracker.reset(); - } - - /** - * Calculates the value of the sync field currently in the message buffer as specified by the offset value. - */ - private long getSyncValue(int dibitOffset) - { - Dibit[] syncDibits = mMessageBuffer.getBuffer(dibitOffset, SYNC_DIBIT_LENGTH); - - long value = 0; - - for(Dibit dibit: syncDibits) - { - value = Long.rotateLeft(value, 2); - value += dibit.getValue(); - } - - return value; - } - - /** - * Processes dibits received without a sync condition - * @param dibitCount with sync loss - */ - private void processSyncLossDibits(int dibitCount, int timeslot) - { - mDibitCounter -= dibitCount; - - if(mBurstDetectListener != null) - { - mBurstDetectListener.syncLost(dibitCount * 2, timeslot); - } - } - - /** - * Tracks the sync state for a timeslot to account for voice framing that does not transmit a sync pattern in each - * frame. - */ - public class SyncTracker - { - private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN; - private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL; - - /** - * Updates the sync pattern for the most recently detected burst to the specified pattern. When the previous - * sync pattern is one of either base or mobile voice frame and the detected pattern is unknown, automatically - * advances the sync pattern to the next voice frame in the super frame sequence to account for voice - * frames B - F that do not have a sync pattern. - */ - public void update(DMRSyncPattern pattern, QPSKCarrierLock carrierLock) - { - if(pattern == DMRSyncPattern.UNKNOWN) - { - switch(mSyncPattern) - { - case BASE_STATION_VOICE: //Voice frame A - mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_B; - break; - case BS_VOICE_FRAME_B: - mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_C; - break; - case BS_VOICE_FRAME_C: - mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_D; - break; - case BS_VOICE_FRAME_D: - mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_E; - break; - case BS_VOICE_FRAME_E: - mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_F; - break; - - case MOBILE_STATION_VOICE: //Voice frame A - case DIRECT_MODE_VOICE_TIMESLOT_1: - case DIRECT_MODE_VOICE_TIMESLOT_2: - mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_B; - break; - case MS_VOICE_FRAME_B: - mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_C; - break; - case MS_VOICE_FRAME_C: - mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_D; - break; - case MS_VOICE_FRAME_D: - mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_E; - break; - case MS_VOICE_FRAME_E: - mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_F; - break; - default: - mSyncPattern = pattern; - mCarrierLock = carrierLock; - break; - } - } - else - { - mSyncPattern = pattern; - mCarrierLock = carrierLock; - } - } - - /** - * Most recently detected sync pattern - */ - public DMRSyncPattern getSyncPattern() - { - return mSyncPattern; - } - - /** - * Explicitly sets the pattern for this tracker - */ - public void setSyncPattern(DMRSyncPattern pattern) - { - mSyncPattern = pattern; - } - - /** - * Carrier lock as indicated by the sync detector. - */ - public QPSKCarrierLock getCarrierLock() - { - return mCarrierLock; - } - - /** - * Indicates if the burst has a QPSK constellation mis-alignment that has to be corrected - */ - public boolean hasCarrierMisAlignment() - { - return getCarrierLock() != QPSKCarrierLock.NORMAL; - } - - /** - * Checks the contents of the message buffer to determine if the message has a valid sync pattern, or if the - * message is a continuation voice frame from a voice super frame. The identified sync pattern and number of - * bit errors for that pattern are stored in the sync detector so that they can be accessed for dispatching - * a message. - */ - public boolean hasSync() - { - return getSyncPattern() != DMRSyncPattern.UNKNOWN; - } - - /** - * Resets the sync pattern to unknown - */ - public void reset() - { - mSyncPattern = DMRSyncPattern.UNKNOWN; - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DmrCrcMaskManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRCrcMaskManager.java similarity index 97% rename from src/main/java/io/github/dsheirer/module/decode/dmr/DmrCrcMaskManager.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/DMRCrcMaskManager.java index 01e54dde..33c48536 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DmrCrcMaskManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRCrcMaskManager.java @@ -21,18 +21,13 @@ package io.github.dsheirer.module.decode.dmr; import io.github.dsheirer.edac.CRCDMR; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.TimeUnit; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,9 +44,9 @@ import org.slf4j.LoggerFactory; * values against CSBK messages and some other function is involved in determining which CRC mask value to use when * checking the CRC of the transmitted CSBK ... fun and games! */ -public class DmrCrcMaskManager +public class DMRCrcMaskManager { - private Logger LOGGER = LoggerFactory.getLogger(DmrCrcMaskManager.class); + private Logger LOGGER = LoggerFactory.getLogger(DMRCrcMaskManager.class); private Map mCsbkTrackerMapTS1 = new TreeMap<>(); private Map mCsbkTrackerMapTS2 = new TreeMap<>(); private Set mSortedTrackersTS1 = new TreeSet<>(); @@ -60,7 +55,7 @@ public class DmrCrcMaskManager /** * Constructs an instance */ - public DmrCrcMaskManager() + public DMRCrcMaskManager() { } 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 d2f093a7..7a57a3fe 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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,22 +19,26 @@ package io.github.dsheirer.module.decode.dmr; +import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; 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.psk.dqpsk.DQPSKDemodulator; +import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorFactory; import io.github.dsheirer.dsp.squelch.PowerMonitor; -import io.github.dsheirer.dsp.symbol.Dibit; -import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.SyncLossMessage; +import io.github.dsheirer.module.decode.Decoder; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.FeedbackDecoder; -import io.github.dsheirer.sample.Broadcaster; +import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule; +import io.github.dsheirer.module.decode.dmr.message.DMRBurst; +import io.github.dsheirer.module.decode.dmr.message.DMRMessage; +import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl; +import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.IByteBufferProvider; import io.github.dsheirer.sample.complex.ComplexSamples; @@ -44,7 +48,9 @@ import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.wave.ComplexWaveSource; import java.io.File; +import java.io.IOException; import java.nio.ByteBuffer; +import java.text.DecimalFormat; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -52,108 +58,82 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * DMR decoder module. + * DMR DQPSK decoder. + * + * Processes the sample stream using an optimal (scalar or vector) DQPSK demodulator. The demodulated sample stream + * is processed by the DMRSoftSymbolProcessor at each symbol time interval using a primary and two lagging soft sync + * detectors. The sync detectors use correlation of the demodulated soft symbols with all known sync patterns to + * detect the sync and align the symbol timing and symbol period to the optimal location and symbol period as + * calculated across each detected burst. Once the symbol processor has fingerprinted the channel type, it optimizes + * sync detection for one of three detected modes (BASE, MOBILE or DIRECT) to reduce sync correlation workload. + * + * The DMRSoftSymbolMessageFramer receives sync detections from the soft symbol processor and buffers symbols from the + * start of the burst through the sync so that the symbols can be resampled when the sync is detected, ensuring that + * the burst symbols are accurate using the symbol timing and period values calculated on the sync detection. + * + * For Direct Mode where one timeslot is empty, the symbol message framer uses two message burst buffers that alternate + * for each burst so that one buffer tracks the active timeslot and the other buffer tracks the empty timeslot to + * ensure accurate burst detection and tracking. + * + * The DMRMessageProcessor processes messages from the message framer to extract and reassemble link control and + * embedded link control parameters. */ -public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider, - IComplexSamplesListener, Listener, IByteBufferProvider +public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplexSamplesListener, ISourceEventListener, + ISourceEventProvider, Listener { - private final static Logger mLog = LoggerFactory.getLogger(DMRDecoder.class); - protected static final float SAMPLE_COUNTER_GAIN = 0.4f; - private static final double SYMBOL_RATE = 4800.0; - private double mSampleRate; - private Broadcaster mDibitBroadcaster = new Broadcaster<>(); - private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300); + private static final Logger LOGGER = LoggerFactory.getLogger(DMRDecoder.class); + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); + private static final int SYMBOL_RATE = 4800; + private static final Map BASEBAND_FILTERS = new HashMap<>(); + private DQPSKDemodulator mDemodulator; + private DMRMessageFramer mMessageFramer = new DMRMessageFramer(); + private DMRSoftSymbolProcessor mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer); private DMRMessageProcessor mMessageProcessor; - protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl(); - private Map mBasebandFilters = new HashMap<>(); - protected IRealFilter mIBasebandFilter; - protected IRealFilter mQBasebandFilter; - - protected InterpolatingSampleBuffer mInterpolatingSampleBuffer; - protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator; - protected CostasLoop mCostasLoop; - protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor; - protected DMRMessageFramer mMessageFramer; - protected PowerMonitor mPowerMonitor = new PowerMonitor(); + private IRealFilter mIBasebandFilter; + private IRealFilter mQBasebandFilter; + private PowerMonitor mPowerMonitor = new PowerMonitor(); /** * Constructs an instance + * @param config for the DMR decoder */ - public DMRDecoder(DecodeConfigDMR config) + public DMRDecoder(DecodeConfigDMR config, boolean isTrafficChannel) { mMessageProcessor = new DMRMessageProcessor(config); mMessageProcessor.setMessageListener(getMessageListener()); - getDibitBroadcaster().addListener(mByteBufferAssembler); setSampleRate(25000.0); + + if(isTrafficChannel) + { + mSymbolProcessor.setBaseStationMode(true); + } } - @Override - public void start() - { - super.start(); - mQPSKDemodulator.start(); - } - - @Override - public void stop() - { - super.stop(); - mQPSKDemodulator.stop(); - } - - /** - * DMR decoder type. - */ @Override public DecoderType getDecoderType() { return DecoderType.DMR; } - /** - * Message processor - */ - protected DMRMessageProcessor getMessageProcessor() - { - return mMessageProcessor; - } - /** * Sets the sample rate and configures internal decoder components. * @param sampleRate of the channel to decode */ public void setSampleRate(double sampleRate) { - if(sampleRate <= getSymbolRate() * 2) + if(sampleRate <= SYMBOL_RATE * 2) { throw new IllegalArgumentException("Sample rate [" + sampleRate + "] must be >9600 (2 * " + - getSymbolRate() + " symbol rate)"); + SYMBOL_RATE + " symbol rate)"); } mPowerMonitor.setSampleRate((int)sampleRate); - mSampleRate = sampleRate; - mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); - mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter()); - mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); - mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300); - mFrequencyCorrectionSyncMonitor = new FrequencyCorrectionSyncMonitor(mCostasLoop, this); - mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN); - - mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer); - - if(mMessageFramer != null) - { - getDibitBroadcaster().removeListener(mMessageFramer); - } - - //The Costas Loop receives symbol-inversion correction requests when detected. - //The PLL gain monitor receives sync detect/loss signals from the message framer - mMessageFramer = new DMRMessageFramer(mCostasLoop); - mMessageFramer.setSyncDetectListener(mFrequencyCorrectionSyncMonitor); - mMessageFramer.setListener(getMessageProcessor()); - - mQPSKDemodulator.setSymbolListener(getDibitBroadcaster()); - getDibitBroadcaster().addListener(mMessageFramer); + mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate)); + mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate)); + mDemodulator = DQPSKDemodulatorFactory.getDemodulator(sampleRate, SYMBOL_RATE); + mSymbolProcessor.setSamplesPerSymbol(mDemodulator.getSamplesPerSymbol()); + mMessageFramer.setListener(mMessageProcessor); + mMessageProcessor.setMessageListener(getMessageListener()); } /** @@ -163,142 +143,86 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, @Override public void receive(ComplexSamples samples) { - mMessageFramer.setCurrentTime(samples.timestamp()); - + mMessageFramer.setTimestamp(samples.timestamp()); float[] i = mIBasebandFilter.filter(samples.i()); float[] q = mQBasebandFilter.filter(samples.q()); //Process buffer for power measurements mPowerMonitor.process(i, q); - ComplexSamples amplified = mAGC.process(i, q, samples.timestamp()); - mQPSKDemodulator.receive(amplified); + float[] demodulated = mDemodulator.demodulate(i, q); + mSymbolProcessor.receive(demodulated); } /** * Constructs a baseband filter for this decoder using the current sample rate */ - private float[] getBasebandFilter() + private float[] getBasebandFilter(double sampleRate) { - //Attempt to reuse a cached (ie already-designed) filter if available - float[] filter = mBasebandFilters.get(getSampleRate()); - - if(filter == null) + if(BASEBAND_FILTERS.containsKey(sampleRate)) { - FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate((int)getSampleRate()) + return BASEBAND_FILTERS.get(sampleRate); + } + + FIRFilterSpecification specification = FIRFilterSpecification + .lowPassBuilder() + .sampleRate(sampleRate) .passBandCutoff(5100) - .passBandAmplitude(1.0) - .passBandRipple(0.01) - .stopBandAmplitude(0.0) - .stopBandStart(6500) - .stopBandRipple(0.01) - .build(); + .passBandAmplitude(1.0).passBandRipple(0.01) //.01 + .stopBandAmplitude(0.0).stopBandStart(6500) //6500 + .stopBandRipple(0.01).build(); - try - { - filter = FilterFactory.getTaps(specification);// - } - catch(Exception fde) //FilterDesignException - { - mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate()); - } + float[] coefficients = null; - if(filter != null) - { - mBasebandFilters.put(getSampleRate(), filter); - } - else - { - throw new IllegalStateException("Couldn't design a DMR baseband filter for sample rate: " + getSampleRate()); - } - } - - return filter; - } - - /** - * Process source events - */ - protected void process(SourceEvent sourceEvent) - { - switch(sourceEvent.getEvent()) + try { - case NOTIFICATION_SAMPLE_RATE_CHANGE: - mCostasLoop.reset(); - setSampleRate(sourceEvent.getValue().doubleValue()); - break; - case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE: - //Reset the PLL if/when the tuner PPM changes so that we can re-lock - mCostasLoop.reset(); - break; + coefficients = FilterFactory.getTaps(specification); + BASEBAND_FILTERS.put(sampleRate, coefficients); } + catch(Exception fde) //FilterDesignException + { + System.out.println("Error"); + } + + if(coefficients == null) + { + throw new IllegalStateException("Unable to design low pass filter for sample rate [" + sampleRate + "]"); + } + + return coefficients; } /** - * Resets this decoder to prepare for processing a new channel - */ - @Override - public void reset() - { - mCostasLoop.reset(); - mFrequencyCorrectionSyncMonitor.reset(); - } - - /** - * Assembler for packaging Dibit stream into reusable byte buffers. - */ - protected Broadcaster getDibitBroadcaster() - { - return mDibitBroadcaster; - } - - /** - * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler + * Implements the IByteBufferProvider interface - delegates to the symbol processor */ @Override public void setBufferListener(Listener listener) { - mByteBufferAssembler.setBufferListener(listener); + mSymbolProcessor.setBufferListener(listener); } /** - * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler + * Implements the IByteBufferProvider interface - delegates to the symbol processor */ @Override public void removeBufferListener(Listener listener) { - mByteBufferAssembler.removeBufferListener(listener); + mSymbolProcessor.setBufferListener(null); } /** - * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler + * Implements the IByteBufferProvider interface - delegates to the symbol processor */ @Override public boolean hasBufferListeners() { - return mByteBufferAssembler.hasBufferListeners(); + return mSymbolProcessor.hasBufferListener(); } - protected double getSymbolRate() + @Override + public Listener getSourceEventListener() { - return SYMBOL_RATE; - } - - /** - * Current sample rate for this decoder - */ - protected double getSampleRate() - { - return mSampleRate; - } - - /** - * Samples per symbol based on current sample rate and symbol rate. - */ - public float getSamplesPerSymbol() - { - return (float)(getSampleRate() / getSymbolRate()); + return this::process; } /** @@ -307,66 +231,194 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, @Override public void setSourceEventListener(Listener listener) { - super.setSourceEventListener(listener); mPowerMonitor.setSourceEventListener(listener); } - /** - * Removes a registered source event listener from receiving source events from this decoder - */ @Override public void removeSourceEventListener() { - super.removeSourceEventListener(); mPowerMonitor.setSourceEventListener(null); } @Override - public Listener getSourceEventListener() + public void start() { - return sourceEvent -> process(sourceEvent); + super.start(); + mMessageFramer.start(); + } + + @Override + public void stop() + { + super.stop(); + mMessageFramer.stop(); } /** - * Listener interface to receive reusable complex buffers + * Process source events */ + private void process(SourceEvent sourceEvent) + { + switch(sourceEvent.getEvent()) + { + case NOTIFICATION_SAMPLE_RATE_CHANGE: + setSampleRate(sourceEvent.getValue().doubleValue()); + break; + } + } + @Override public Listener getComplexSamplesListener() { - return DMRDecoder.this; + return this; } public static void main(String[] args) { - File file = new File("/home/denny/Downloads/TIII,HYT414.585000,.wav"); + LOGGER.info("Starting ..."); - try + // String directory = "D:\\DQPSK Equalizer Research\\"; //Windows + String directory = "/media/denny/T9/DQPSK Equalizer Research/"; //Linux +// String file = directory + "DMR_1_CAPPLUS.wav"; +// String file = directory + "DMR_2_CAPPLUS.wav"; +// String file = directory + "DMR_3_CAPPLUS.wav"; +// String file = directory + "20230819_064211_451250000_SaiaNet_Syracuse_Control_29_baseband.wav"; +// String file = directory + "20230819_064344_454575000_JPJ_Communications_(DMR)_Madison_Control_28_baseband.wav"; +// String file = directory + "DMR_DCDM_4_4_baseband_20220318_142716.wav"; +// String file = directory + "DMR_4_20241213_Saianet.wav"; + String file = directory + "DMR_5_20241217_031219_451425000_SaiaNet_Onondaga_SaiaNet_Control_1_baseband.wav"; +// String file = directory + "DMR_6_20241217_031511_451425000_SaiaNet_Onondaga_SaiaNet_Control_50_baseband.wav"; +// String file = directory + "DMR_7_20241217_031651_451250000_SaiaNet_Onondaga_SaiaNet_Control_50_baseband.wav"; +// String file = directory + "DMR_8_20241217_031845_461662500_SaiaNet_(Tier_III)_Onondaga_Control_25_baseband.wav"; +// String file = directory + "DMR_9_CAPPLUS_encrypted_American_Airlines_Maricopa_Control_29_baseband.wav"; +// String file = directory + "DMR_10_CAP_ENCRYPTED_20241222_035408_935487500_American_Airlines_Maricopa_Control_1_baseband.wav"; + + boolean autoReplay = false; + + DMRDecoder decoder = new DMRDecoder(new DecodeConfigDMR(), false); + decoder.start(); + + UserPreferences userPreferences = new UserPreferences(); + DMRAudioModule audio1 = new DMRAudioModule(userPreferences, new AliasList(""), DMRMessage.TIMESLOT_1); + DMRAudioModule audio2 = new DMRAudioModule(userPreferences, new AliasList(""), DMRMessage.TIMESLOT_2); + + decoder.setMessageListener(new Listener<>() + { + private long mBitCounter = 1; + private int mBitErrorCounter; + private int mValidMessageCounter; + private int mTotalMessageCounter; + + @Override + public void receive(IMessage iMessage) + { + int errors = 0; + + if(iMessage.getTimeslot() == DMRMessage.TIMESLOT_1) + { + audio1.receive(iMessage); + } + if(iMessage.getTimeslot() == DMRMessage.TIMESLOT_2) + { + audio2.receive(iMessage); + } + + if(iMessage instanceof DMRBurst burst) + { + mBitCounter += 288; + errors = burst.getMessage().getCorrectedBitCount(); + mTotalMessageCounter++; + + if(mTotalMessageCounter == 492) + { + int a = 0; + } + if(burst.isValid()) + { + mBitErrorCounter += Math.max(errors, 0); + mValidMessageCounter++; + } + } + else if(iMessage instanceof LCMessage lcw) + { + mTotalMessageCounter++; + errors = lcw.getMessage().getCorrectedBitCount(); + if(lcw.isValid()) + { + mBitErrorCounter += errors; + mValidMessageCounter++; + } + } + + double bitErrorRate = (double)mBitErrorCounter / (double)mBitCounter * 100.0; + + boolean logFLC = false; + boolean logSLC = false; + boolean logCACH = false; + boolean logIdles = false; + boolean logEverything = true; + + if(!logEverything && logFLC) + { + if(iMessage instanceof FullLCMessage) + { + System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]"); + } + else if(iMessage instanceof Terminator terminator) + { + System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]"); + } + else if(iMessage instanceof DataMessageWithLinkControl) + { + System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]"); + } + } + + if(!logEverything && logCACH && iMessage instanceof DMRBurst burst && burst.hasCACH()) + { + System.out.println("CACH:" + burst.getCACH()); + } + + if(!logEverything && logSLC && iMessage instanceof ShortLCMessage) + { + System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]"); + } + + if(logEverything) + { + System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]"); + } + + if(!logEverything && logIdles && iMessage instanceof SyncLossMessage) + { + System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]"); + } + } + }); + + try(ComplexWaveSource source = new ComplexWaveSource(new File(file), autoReplay)) { - ComplexWaveSource source = new ComplexWaveSource(file); - source.open(); - System.out.println("Source Sample Rate: " + source.getSampleRate()); - DMRDecoder decoder = new DMRDecoder(new DecodeConfigDMR()); - decoder.setMessageListener(message -> System.out.println("TS:" + message.getTimeslot() + " " + message)); source.setListener(iNativeBuffer -> { Iterator it = iNativeBuffer.iterator(); + while(it.hasNext()) { - ComplexSamples samples = it.next(); - decoder.receive(samples); + decoder.receive(it.next()); } }); - decoder.setSampleRate(source.getSampleRate()); - decoder.start(); source.start(); + decoder.setSampleRate(source.getSampleRate()); while(true) { - source.next(65535); + source.next(2048, true); } } - catch(Exception e) + catch(IOException ioe) { - e.printStackTrace(); + LOGGER.error("Error", ioe); } + + LOGGER.info("Finished"); } } 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 deleted file mode 100644 index cb078cea..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java +++ /dev/null @@ -1,130 +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.module.decode.dmr; - -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.complex.Complex; -import io.github.dsheirer.sample.complex.ComplexSamples; - -/** - * Instrumented version of DMR decoder - */ -public class DMRDecoderInstrumented extends DMRDecoder -{ - private Listener mPLLPhaseErrorListener; - private Listener mPLLFrequencyListener; - private Listener mSamplesPerSymbolListener; - private Listener mComplexSymbolListener; - private Listener mFilteredSymbolListener; - private Listener mSymbolDecisionDataListener; - - /** - * Instrumented version of the DMR decoder that supports registering listeners to provide access to data as - * it is being processed by the decoder. - */ - public DMRDecoderInstrumented(DecodeConfigDMR config) - { - super(config); - } - - /** - * Primary method for processing incoming complex sample buffers - * @param complexSamples containing channelized complex samples - */ - @Override - public void receive(ComplexSamples samples) - { - mMessageFramer.setCurrentTime(samples.timestamp()); - - float[] i = mIBasebandFilter.filter(samples.i()); - float[] q = mQBasebandFilter.filter(samples.q()); - - if(mFilteredSymbolListener != null) - { - mFilteredSymbolListener.receive(new ComplexSamples(i, q, samples.timestamp())); - } - - //Process buffer for power measurements - mPowerMonitor.process(i, q); - - ComplexSamples amplified = mAGC.process(i, q, samples.timestamp()); - mQPSKDemodulator.receive(amplified); - } - - /** - * Overrides this method so we can correctly configure for instrumented operations - */ - public void setSampleRate(double sampleRate) - { - super.setSampleRate(sampleRate); - - InterpolatingSampleBufferInstrumented instrumentedBuffer = - new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN); - mInterpolatingSampleBuffer = instrumentedBuffer; - - DQPSKDecisionDirectedDemodulatorInstrumented instrumented = new DQPSKDecisionDirectedDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate()); - mQPSKDemodulator = instrumented; - - instrumented.setComplexSymbolListener(mComplexSymbolListener); - instrumented.setPLLErrorListener(mPLLPhaseErrorListener); - instrumented.setPLLFrequencyListener(mPLLFrequencyListener); - instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener); - instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener); - instrumented.setSymbolListener(getDibitBroadcaster()); - getDibitBroadcaster().addListener(mMessageFramer); - } - - public void setComplexSymbolListener(Listener listener) - { - mComplexSymbolListener = listener; - ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setComplexSymbolListener(listener); - } - - public void setPLLPhaseErrorListener(Listener listener) - { - mPLLPhaseErrorListener = listener; - ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener); - } - - public void setPLLFrequencyListener(Listener listener) - { - mPLLFrequencyListener = listener; - ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener); - } - - public void setFilteredBufferListener(Listener listener) - { - mFilteredSymbolListener = listener; - } - - public void setSymbolDecisionDataListener(Listener listener) - { - mSymbolDecisionDataListener = listener; - ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener); - } - - public void setSamplesPerSymbolListener(Listener listener) - { - mSamplesPerSymbolListener = listener; - ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index e41e1fac..af5018b3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -88,8 +88,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; +import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters; -import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.IDecodeEvent; @@ -546,7 +547,7 @@ public class DMRDecoderState extends TimeslotDecoderState { if(message.getSyncPattern().isMobileSyncPattern()) { - if(message.getSyncPattern().isDirectMode()) + if(message.getSyncPattern().isDirect()) { updateCurrentCall(DecodeEventType.CALL, "DIRECT MODE", message.getTimestamp()); } @@ -569,7 +570,7 @@ public class DMRDecoderState extends TimeslotDecoderState { EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters(); - if(embedded.getShortBurst() instanceof EncryptionParameters arc4) + if(embedded.getShortBurst() instanceof EmbeddedEncryptionParameters arc4) { updateEncryptedCall(arc4, true, voiceEmb.getTimestamp()); } @@ -1356,10 +1357,10 @@ public class DMRDecoderState extends TimeslotDecoderState /** * Updates the current call with encryption information. - * @param encryptionParameters decoded from the Voice Frame F + * @param embeddedEncryptionParameters decoded from the Voice Frame F * @param isGroup true for group or false for individual call. */ - private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp) + private void updateEncryptedCall(EmbeddedEncryptionParameters embeddedEncryptionParameters, boolean isGroup, long timestamp) { if(mCurrentCallEvent != null) { @@ -1367,11 +1368,11 @@ public class DMRDecoderState extends TimeslotDecoderState if(details == null) { - details = encryptionParameters.toString(); + details = embeddedEncryptionParameters.toString(); } - else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION")) + else if(!details.contains(embeddedEncryptionParameters.toString()) && !details.contains("ENCRYPTION")) { - details += " " + encryptionParameters; + details += " " + embeddedEncryptionParameters; } mCurrentCallEvent.setDetails(details); @@ -1381,7 +1382,7 @@ public class DMRDecoderState extends TimeslotDecoderState mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_ENCRYPTED, timestamp) .channel(getCurrentChannel()) - .details(encryptionParameters.toString()) + .details(embeddedEncryptionParameters.toString()) .identifiers(getIdentifierCollection().copyOf()) .timeslot(getTimeslot()) .build(); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java new file mode 100644 index 00000000..596ecff3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java @@ -0,0 +1,78 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr; + +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.module.decode.dmr.sync.DMRHardSyncDetector; +import java.nio.ByteBuffer; + +/** + * Processes a stream of dibit symbol decisions to detect DMR sync patterns and produce decoded DMR messages. + * + * Note: this symbol processor is primarily used for offline analysis of a captured bit stream recording. + */ +public class DMRHardSymbolProcessor +{ + private final DMRMessageFramer mMessageFramer; + private final DMRHardSyncDetector mSyncDetector = new DMRHardSyncDetector(); + //Dibit delay line sizing: CACH(12) + MESSAGE_PREFIX(54) + SYNC(24) + private DibitDelayLine mDibitDelayLine = new DibitDelayLine(90); + + + /** + * Constructs an instance + * @param messageFramer to receive dibits and sync notifications. + */ + public DMRHardSymbolProcessor(DMRMessageFramer messageFramer) + { + mMessageFramer = messageFramer; + } + + /** + * Processes an individual dibit. + * @param dibit to process + */ + public void process(Dibit dibit) + { + Dibit ejected = mDibitDelayLine.insert(dibit); + mMessageFramer.receive(ejected); + + if(mSyncDetector.process(dibit)) + { + mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern()); + } + } + + /** + * Primary method for streaming decoded symbol byte arrays. + * + * @param buffer to process into a stream of dibits for processing. + */ + public void receive(ByteBuffer buffer) + { + for(byte value : buffer.array()) + { + for(int x = 0; x <= 3; x++) + { + process(Dibit.parse(value, x)); + } + } + } +} 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 357e42de..16b20496 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-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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,517 +16,375 @@ * along with this program. If not, see * **************************************************************************** */ + package io.github.dsheirer.module.decode.dmr; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.dsp.symbol.Dibit; -import io.github.dsheirer.dsp.symbol.ISyncDetectListener; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.SyncLossMessage; -import io.github.dsheirer.module.decode.dmr.audio.DMRCallSequenceRecorder; import io.github.dsheirer.module.decode.dmr.message.CACH; -import io.github.dsheirer.module.decode.dmr.message.DMRBurst; import io.github.dsheirer.module.decode.dmr.message.DMRMessage; import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory; -import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage; -import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket; -import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Identity; -import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Token; -import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; 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 java.io.IOException; -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.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Arrays; /** - * DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors - * and issue phase corrections. + * DMR message framer. Processes a DMR symbol stream to produce DMR messages/bursts. Employs two symbol buffers to + * track and frame both DMR timeslots. Responds to external sync detections to alternately activate one of the two + * buffers and capture a DMR burst, convert it to the correct message format and send it to a message listener. + * + * This framer is able to detect DMR Direct mode and track one active and one inactive timeslot across each voice + * super frame in addition to framing base and mobile station data and voice bursts. */ -public class DMRMessageFramer implements Listener, IDMRBurstDetectListener +public class DMRMessageFramer implements Listener { - private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFramer.class); - - private static final double DMR_BIT_RATE = 9600.0; - - /** - * Provides sync detection and burst framing - */ - private DMRBurstFramer mBurstFramer; - - /** - * External listener for messages produced by this framer - */ + private static final int DIBIT_CACH_START = 0; + private static final int DIBIT_BURST_END = 144; private Listener mMessageListener; - - /** - * External listener for sync state information - */ - private ISyncDetectListener mSyncDetectListener; - - /** - * Tracks current time for use with framed bursts/messages. This value is updated externally from sample buffer - * timestamps and internally as each framed message is received or sync loss is processed. - */ - private long mCurrentTime = System.currentTimeMillis(); + private Dibit[] mBufferA = new Dibit[144]; + private Dibit[] mBufferB = new Dibit[144]; + private int mBufferAPointer = 0; + private int mBufferBPointer = 0; + private DMRSyncPattern mBufferAPattern = DMRSyncPattern.UNKNOWN; + private DMRSyncPattern mBufferBPattern = DMRSyncPattern.UNKNOWN; + private int mBufferATimeslot; + private int mBufferBTimeslot; + private boolean mBufferAActive = false; + private boolean mAssemblingBurst = false; + private int mDibitCounter = 0; + private int mDibitSinceTimestampCounter = 0; + private long mReferenceTimestamp = 0; + private boolean mRunning = false; /** * Constructs an instance - * - * @param phaseLockedLoop to receive PLL phase lock error corrections that are identified by the DMRBurstFramer - * when known phase misaligned sync patterns are detected. - */ - public DMRMessageFramer(IPhaseLockedLoop phaseLockedLoop) - { - mBurstFramer = new DMRBurstFramer(this, phaseLockedLoop); - } - - /** - * Constructs an instance. */ public DMRMessageFramer() { - this(null); + Arrays.fill(mBufferA, Dibit.D00_PLUS_1); + Arrays.fill(mBufferB, Dibit.D00_PLUS_1); } /** - * Registers a sync detect listener to be notified each time a sync pattern and NID are detected. - */ - public void setSyncDetectListener(ISyncDetectListener syncDetectListener) - { - mSyncDetectListener = syncDetectListener; - } - - /** - * Current timestamp or timestamp of incoming message buffers that is continuously updated to as - * close as possible to the bits processed for the expected baud rate. - * - * @return current time - */ - private long getTimestamp() - { - return mCurrentTime; - } - - /** - * Sets the current time. This should be invoked by an incoming message buffer stream. - * - * @param currentTime - */ - public void setCurrentTime(long currentTime) - { - mCurrentTime = currentTime; - } - - /** - * Updates the current timestamp based on the number of bits processed versus the bit rate per second - * in order to keep an accurate running timestamp to use for timestamped message creation. - * - * @param bitsProcessed thus far - */ - private void updateBitsProcessed(int bitsProcessed) - { - if(bitsProcessed > 0) - { - mCurrentTime += (long)((double)bitsProcessed / DMR_BIT_RATE * 1000.0); - } - } - - /** - * Registers the listener for messages produced by this message framer - * - * @param messageListener to receive framed and decoded messages - */ - public void setListener(Listener messageListener) - { - mMessageListener = messageListener; - } - - /** - * Primary method for streaming decoded symbol dibits for message framing. - * - * @param dibit to process + * Primary input method for receiving the stream of demodulated dibit symbols. + * @param dibit to process. */ @Override public void receive(Dibit dibit) { - mBurstFramer.receive(dibit); - } + mDibitCounter++; + mDibitSinceTimestampCounter++; - /** - * Primary method for streaming decoded symbol byte arrays. - * - * @param buffer to process into a stream of dibits for processing. - */ - public void receive(ByteBuffer buffer) - { - for(byte value : buffer.array()) + //Issue a sync loss for each full 1-second interval (ie 4800 dibits) of no sync. + if(!mAssemblingBurst && mDibitCounter >= 4800) { - for(int x = 0; x <= 3; x++) + dispatch(new SyncLossMessage(getTimestamp(), 9600, Protocol.DMR, DMRMessage.TIMESLOT_0)); + mDibitCounter -= 4800; + mBufferAPattern = DMRSyncPattern.UNKNOWN; + mBufferBPattern = DMRSyncPattern.UNKNOWN; + } + + if(mAssemblingBurst) + { + if(mBufferAActive) { - receive(Dibit.parse(value, x)); - } - } - } + mBufferA[mBufferAPointer++] = dibit; - /** - * DMR burst detection processing. Processes DMR burst/messages from external burst framer/producer. - * - * @param message bits for the burst/message and any bit errors detected in the sync pattern - * @param syncPattern that was detected for the burst - */ - @Override - public void burstDetected(CorrectedBinaryMessage message, DMRSyncPattern syncPattern, int timeslot) - { - //Each burst is 288 bits - updateBitsProcessed(288); - - //This allows the frequency error monitor to be notified when we're in sync - if(mSyncDetectListener != null) - { - mSyncDetectListener.syncDetected(0); - } - - CACH cach = null; - - if(syncPattern.hasCACH()) - { - cach = CACH.getCACH(message); - } - - if(mMessageListener != null) - { - DMRMessage dmrMessage = DMRMessageFactory.create(syncPattern, message, cach, getTimestamp(), timeslot); - - if(dmrMessage != null) - { - mMessageListener.receive(dmrMessage); - } - } - } - - /** - * Processes a sync loss to track how many bits were processed and update message listeners. - * - * @param bitsProcessed since the last sync detect - */ - @Override - public void syncLost(int bitsProcessed, int timeslot) - { - updateBitsProcessed(bitsProcessed); - - dispatchSyncLoss(bitsProcessed, timeslot); - - if(mSyncDetectListener != null) - { - mSyncDetectListener.syncLost(bitsProcessed); - } - } - - /** - * Creates a sync loss message and dispatches it to the message listener - * - * @param bitsProcessed without a sync detection - * @param timeslot that lost the sync - */ - private void dispatchSyncLoss(int bitsProcessed, int timeslot) - { - if(bitsProcessed > 0 && mMessageListener != null) - { - mMessageListener.receive(new SyncLossMessage(getTimestamp(), bitsProcessed, Protocol.DMR, timeslot)); - } - } - - public static class MessageListener implements Listener - { - private boolean mHasDMRData = false; - public int mTS0Count = 0; - public int mTS1Count = 0; - - @Override - public void receive(IMessage message) - { - if(message instanceof DMRBurst && ((DMRBurst)message).hasCACH()) - { - CACH cach = ((DMRBurst)message).getCACH(); - mLog.info(cach.toString() + " TS:" + message.getTimeslot() + " " + message.toString()); + if(mBufferAPointer >= mBufferA.length) + { + dispatchBufferA(); + } } else { - mLog.info(" TS:" + message.getTimeslot() + " " + message.toString()); - } + mBufferB[mBufferBPointer++] = dibit; - if(message instanceof DMRPacketMessage dpm) - { - if(dpm.getPacket() instanceof LRRPPacket lrrp) + if(mBufferBPointer >= mBufferB.length) { - mLog.info("LRRP: " + lrrp.toString()); - mLog.info("MESSAGE: " + lrrp.getMessage().toHexString()); + dispatchBufferB(); } } - -// if(message instanceof UDTHeader) -// { -// mLog.info("TS:" + message.getTimeslot() + " " + message.toString()); -// } - -// if(message instanceof UnknownCSBKMessage && message.isValid()) -// { -// mLog.info("TS:" + ((DMRMessage)message).getTimeslot() + " " + message.toString()); -// } - } - - public boolean hasData() - { - return mHasDMRData; - } - - public void reset() - { - mHasDMRData = true; } } - public static class LrrpProcessor implements Listener + private void dispatchBufferA() { - private Map> mLrrpMap = new HashMap<>(); + mDibitCounter -= 144; - public LrrpProcessor() + if(mDibitCounter > 0) { - - } - - @Override - public void receive(IMessage message) - { - if(message instanceof DMRPacketMessage dpm) + //If there are 144 dibits then tag the sync loss to other timeslot, otherwise dump it to timeslot 0. + if(mDibitCounter == 144) { - if(dpm.getPacket() instanceof LRRPPacket lrrp) + //Note: since buffer b got skipped, we'll use buffer a timeslot and then it will get reassigned from + //the CACH to the correct timeslot. + dispatch(new SyncLossMessage(getTimestamp(), 288, Protocol.DMR, mBufferATimeslot)); + } + else + { + dispatch(new SyncLossMessage(getTimestamp(), mDibitCounter * 2, Protocol.DMR, DMRMessage.TIMESLOT_0)); + + if(mDibitCounter > 1 && !mBufferAPattern.isDirect()) { - process(lrrp); + mBufferATimeslot = 0; + mBufferBTimeslot = 0; + mBufferBPattern = DMRSyncPattern.UNKNOWN; } } } - public void process(LRRPPacket lrrpPacket) + mDibitCounter = 0; + + CorrectedBinaryMessage message = getMessage(mBufferA, DIBIT_CACH_START, DIBIT_BURST_END); + CACH cach = CACH.getCACH(message); + + if(mBufferAPattern.hasCACH() && cach.isValid() && mBufferATimeslot != cach.getTimeslot()) { - Identity identity = getIdentity(lrrpPacket); - - if(identity != null) - { - List packets = mLrrpMap.get(identity.getID()); - - if(packets == null) - { - packets = new ArrayList<>(); - } - - packets.add(lrrpPacket); - - mLrrpMap.put(identity.getID(), packets); - } + mBufferATimeslot = cach.getTimeslot(); + mBufferBTimeslot = (mBufferATimeslot == 1 ? 2 : 1); } - public void log() + dispatch(DMRMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot)); + + //Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts + //across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync. + if(mBufferAPattern.isVoicePattern()) { - mLog.info("*************** DUMPING LRRP PACKETS****************************"); - List ids = new ArrayList(mLrrpMap.keySet()); - Collections.sort(ids); - - for(Integer id: ids) - { - mLog.info(""); - mLog.info("###################### ID: " + id + " ###################################"); - List packets = mLrrpMap.get(id); - - for(LRRPPacket lrrp: packets) - { - mLog.info("PACKET: " + lrrp.toString()); - mLog.info("MESSAGE: " + lrrp.getMessage().toHexString()); - } - } + mBufferAPattern = DMRSyncPattern.getNextVoice(mBufferAPattern); } - private static Identity getIdentity(LRRPPacket packet) + //Automatically trigger buffer B burst collection if it is collecting a voice super frame. + if(mBufferBPattern.isVoicePattern()) { - for(Token token: packet.getTokens()) - { - if(token instanceof Identity) - { - return (Identity)token; - } - } - - return null; - } - } - - public static void main(String[] args) - { - String path = "/media/denny/T7 Shield/Recordings/DMR/"; - - //Con+ Traffic + Voice -// String file = path + "20200513_143340_9600BPS_DMR_SaiaNet_Onondaga_Control.bits"; //Enh GPS Revert Window Annce -// String file = path + "20200514_062135_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Neighbor only -// String file = path + "20200514_063507_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 2579 -// String file = path + "20200514_064224_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 5056035 -// String file = path + "20200514_131623_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Grant: 5074193 -// String file = path + "SaiaNet/20200514_133947_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; //<<<<<<<------ Basic Encryption -// String file = path + "20200514_142249_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; -// String file = path + "20200514_144534_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Con+ Control w/GPS Window Announce - - //Con+ Multi-Site with Network Frequency Map - This file is really General Motors Con+ - Tarrant County site -// String file = path + "20200716_235004_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; - - //Cap-Max Tier III CC -// String file = path + "20200710_053632_9600BPS_DMR_Niles_Radio_Coconino_Control.bits"; - - //Cap+ Multi-Site 1 - Data Revert LSN1/2 with CSBKO 62 Site status - String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210133_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits"; -// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210845_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits"; -// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_212309_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits"; - - //Cap+ Multi-Site Enhanced Data Revert Channel (no CSBKO 62) -// String file = path + "Texas_Hunt_Farmers_Electric_Capacity_Plus_Multi_Site/20200714_224018_9600BPS_DMR_Farmers_Electric_Cooperative_Hunt_LCN_3.bits"; //This may have PLL mis-align issues - - //Cap+ Multi-Site - Dallas Unknown -// String file = path + "Texas_Dallas_Unknown_1_Capacity_Plus_Multi_Site/20200716_211233_9600BPS_DMR_Dallas_Unk_Dallas_Unk.bits"; - - //Hytera Short Data = Proprietary (encrypted) -// String file = path + "20200716_222839_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits"; //Hytera short data packets -// String file = path + "20200716_223551_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits"; - - //Motorola GPS Revert channel -// String file = path + "20200730_085433_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits"; - - //Hytera XPT (pseudo trunk) beaconing and voice -// String file = path + "20200729_161450_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //PLL lock oscillating -// String file = path + "20200729_162200_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; -// String file = path + "Hytera XPT/20200816_161126_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //analzing this <<<<<<<<<<<<<<<<<<<<<<<<<< - - //Hytera Tier III - CSBKO 40 -// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; -// String file = path + "20200812_061410_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; -// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; //MBC-channel grant - - //UDT Header And Blocks -// String file = path + "20200730_090634_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits"; - - //Cap+ BP Scrambling -// String file = path + "20200829_065610_9600BPS_DMR_Albany_Medical_Center_Albany_LCN_3.bits"; - -// String file = "/home/denny/SDRTrunk/recordings/20200927_054837_9600BPS_DMR_Blair_Communications_(Capacity_Plus)_Dallas_Control_9.bits"; - - DecodeConfigDMR config = new DecodeConfigDMR(); - - Broadcaster messageBroadcaster = new Broadcaster<>(); - MessageListener messageListener = new MessageListener(); - messageBroadcaster.addListener(messageListener); - - LrrpProcessor lrrpProcessor = new LrrpProcessor(); - messageBroadcaster.addListener(lrrpProcessor); - - DMRCallSequenceRecorder ambeRecorder = new DMRCallSequenceRecorder(new UserPreferences(), 123456789l, - "Denny System", "Denny Site"); - messageBroadcaster.addListener(ambeRecorder); - - boolean multi = false; - - if(multi) - { - try - { - DirectoryStream stream = Files.newDirectoryStream(Paths.get(path), "*.bits"); - - stream.forEach(new Consumer() - { - @Override - public void accept(Path path) - { - mLog.info("Processing: " + path.toString()); - DMRMessageFramer messageFramer = new DMRMessageFramer(null); - DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config); - messageFramer.setListener(messageProcessor); - messageProcessor.setMessageListener(messageBroadcaster); - - try(BinaryReader reader = new BinaryReader(path, 200)) - { - while(reader.hasNext()) - { - ByteBuffer buffer = reader.next(); - messageFramer.receive(buffer); - } - } - catch(Exception ioe) - { - ioe.printStackTrace(); - } - - if(!messageListener.hasData()) - { -// mLog.info("Has Data: " + listener.hasData() + " File:" + path.toString()); -// try -// { -// Files.delete(path); -// } -// catch(IOException ioe) -// { -// ioe.printStackTrace(); -// } - } - - messageListener.reset(); - - System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count); - messageListener.mTS0Count = 0; - messageListener.mTS1Count = 0; - } - }); - } - catch(IOException ioe) - { - ioe.printStackTrace(); - } - - lrrpProcessor.log(); + mAssemblingBurst = true; + mBufferAActive = false; + mBufferBPointer = 0; } else { - DMRMessageFramer messageFramer = new DMRMessageFramer(null); - DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config); - messageFramer.setListener(messageProcessor); - messageProcessor.setMessageListener(messageBroadcaster); + mAssemblingBurst = false; + } + } - try(BinaryReader reader = new BinaryReader(Path.of(file), 200)) + private void dispatchBufferB() + { + mDibitCounter -= 144; + + if(mDibitCounter > 0) + { + //If there are 144 dibits then tag the sync loss to other timeslot, otherwise dump it to timeslot 0. + if(mDibitCounter == 144) { - while(reader.hasNext()) + //Note: since buffer a got skipped, we'll use buffer b timeslot and then it will get reassigned from + //the CACH to the correct timeslot. + dispatch(new SyncLossMessage(getTimestamp(), 288, Protocol.DMR, mBufferBTimeslot)); + } + else + { + dispatch(new SyncLossMessage(getTimestamp(), mDibitCounter * 2, Protocol.DMR, DMRMessage.TIMESLOT_0)); + + if(mDibitCounter > 1 && !mBufferBPattern.isDirect()) { - ByteBuffer buffer = reader.next(); - messageFramer.receive(buffer); + mBufferATimeslot = 0; + mBufferBTimeslot = 0; + mBufferAPattern = DMRSyncPattern.UNKNOWN; } } - catch(Exception ioe) - { - ioe.printStackTrace(); - } + } - System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count); - messageListener.mTS0Count = 0; - messageListener.mTS1Count = 0; + mDibitCounter = 0; + + CorrectedBinaryMessage burst = getMessage(mBufferB, DIBIT_CACH_START, DIBIT_BURST_END); + CACH cach = CACH.getCACH(burst); + + if(mBufferBPattern.hasCACH() && cach.isValid() && mBufferBTimeslot != cach.getTimeslot()) + { + mBufferBTimeslot = cach.getTimeslot(); + mBufferATimeslot = (mBufferBTimeslot == 1 ? 2 : 1); + } + + dispatch(DMRMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot)); + + //Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts + //across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync. + if(mBufferBPattern.isVoicePattern()) + { + mBufferBPattern = DMRSyncPattern.getNextVoice(mBufferBPattern); + } + + //Automatically trigger buffer A burst collection if it is collecting a voice super frame. + if(mBufferAPattern.isVoicePattern()) + { + mAssemblingBurst = true; + mBufferAActive = true; + mBufferAPointer = 0; + } + else + { + mAssemblingBurst = false; + } + } + + /** + * Dispatches the message to the registered message listener. + * @param message + */ + private void dispatch(IMessage message) + { + if(mRunning && mMessageListener != null) + { + mMessageListener.receive(message); + } + } + + /** + * Starts this framer dispatching messages + */ + public void start() + { + mRunning = true; + } + + /** + * Stops this framer from dispatching messages + */ + public void stop() + { + mRunning = false; + } + + /** + * Sets the listener to receive framed DMR messages. + * @param listener for messages. + */ + public void setListener(Listener listener) + { + mMessageListener = listener; + } + + /** + * Creates a binary message from the dibit buffer. + * @param buffer containing dibits + * @param start index inclusive + * @param end index exclusive. + * @return binary message + */ + private CorrectedBinaryMessage getMessage(Dibit[] buffer, int start, int end) + { + CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (end - start)); + + Dibit dibit = null; + for(int i = start; i < end; i++) + { + dibit = buffer[i]; + message.add(dibit.getBit1(), dibit.getBit2()); + } + + return message; + } + + /** + * Externally triggered sync detection to initiate DMR burst capture. Indicates a sync pattern is detected and the + * next received dibit symbol is the first dibit of the DMR burst. + * @param pattern detected + */ + public void syncDetected(DMRSyncPattern pattern) + { + if(mAssemblingBurst) + { + if(mBufferAActive && mBufferAPointer < mBufferA.length) + { + mBufferAPointer = 144; + mBufferAPattern = DMRSyncPattern.UNKNOWN; + } + else if(!mBufferAActive && mBufferBPointer < mBufferB.length) + { + mBufferBPointer = 144; + mBufferBPattern = DMRSyncPattern.UNKNOWN; + } + } + + mAssemblingBurst = true; + + if(mBufferAActive) + { + mBufferAActive = false; + mBufferBPointer = 0; + mBufferBPattern = pattern; + + if(pattern.isDirect()) + { + if(pattern.isDirectTS1()) + { + mBufferATimeslot = 2; + mBufferBTimeslot = 1; + } + else if(pattern.isDirectTS2()) + { + mBufferATimeslot = 1; + mBufferBTimeslot = 2; + } + + if(mBufferAPattern == DMRSyncPattern.UNKNOWN) + { + mBufferAPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT; + } + } + } + else + { + mBufferAActive = true; + mBufferAPointer = 0; + mBufferAPattern = pattern; + + if(pattern.isDirect()) + { + if(pattern.isDirectTS1()) + { + mBufferATimeslot = 1; + mBufferBTimeslot = 2; + } + else if(pattern.isDirectTS2()) + { + mBufferATimeslot = 2; + mBufferBTimeslot = 1; + } + + if(mBufferBPattern == DMRSyncPattern.UNKNOWN) + { + mBufferBPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT; + } + } + } + } + + /** + * Sets or updates the current dibit stream time from an incoming sample buffer. + * @param time to use as a reference timestamp. + */ + public void setTimestamp(long time) + { + mReferenceTimestamp = time; + mDibitSinceTimestampCounter = 0; + } + + /** + * Calculates the timestamp accurate to the currently received dibit. + * @return timestamp in milliseconds. + */ + private long getTimestamp() + { + if(mReferenceTimestamp > 0) + { + return mReferenceTimestamp + (long)(1000.0 * mDibitSinceTimestampCounter / 4800); + } + else + { + mDibitSinceTimestampCounter = 0; + return System.currentTimeMillis(); } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java index fc19560e..f0d9cbdc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java @@ -20,6 +20,7 @@ package io.github.dsheirer.module.decode.dmr; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; @@ -73,7 +74,7 @@ public class DMRMessageProcessor implements Listener private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler(); private Listener mMessageListener; private Map mTimeslotFrequencyMap = new TreeMap<>(); - private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager(); + private DMRCrcMaskManager mCrcMaskManager = new DMRCrcMaskManager(); private boolean mIgnoreCrcChecksums; /** @@ -115,6 +116,13 @@ public class DMRMessageProcessor implements Listener return; } + if(message instanceof SyncLossMessage) + { + mSLCAssembler.reset(); + mFLCAssemblerTimeslot1.reset(); + mFLCAssemblerTimeslot2.reset(); + } + //Detect and correct messages employing an alternate CRC mask pattern. if(!message.isValid() && message instanceof CSBKMessage csbk) { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java new file mode 100644 index 00000000..c8b063f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java @@ -0,0 +1,458 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr; + +import io.github.dsheirer.dsp.filter.interpolator.PhaseAwareLinearInterpolator; +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorFactory; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncDetectMode; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncModeMonitor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; +import io.github.dsheirer.sample.Listener; +import java.nio.ByteBuffer; + +/** + * DMR soft symbol processor. Processes a stream of differentially decoded samples to align to the symbol timing and + * symbol period and extract symbols from the sample stream. + * + * Uses three soft sync correlation detectors to achieve initial sync lock with a primary sync detector and two lagging + * sync detectors sampling at 1/3 and 2/3 of the symbol period to ensure the sync is detected while only calculating at + * each symbol period interval. Once a sync is detected, sync lock is declared and the lagging sync detectors are + * disabled. Sync lock disables whenever the quantity of processed symbols exceeds one DMR burst length and the + * lagging detectors re-enable. + * + * The sync mode monitor processes the sync detections to identify the dominant sync mode for the channel and then + * disables processing of the two unused sync modes (BASE STATION, MOBILE STATION, or DIRECT MODE) for the remainder + * of the channel. + * + * This processor uses a symbol delay buffer (dibit delay line) to retain a partial DMR burst from CACH up through the + * sync field of the burst. This allows the optimize method to resample the burst symbols once the sync is detected and + * optimal symbol timing and symbol period are calculated. This ensures the best symbol decisions are then passed to + * the message framer along with the sync detection. + * + * Buffer Structure & Size = 129 Dibits: [2 pad][12 CACH][54 Message Prefix][24 Sync][24 workspace][1 pad] + */ +public class DMRSoftSymbolProcessor +{ + private static final int BUFFER_PROTECTED_REGION_DIBITS = 92; + private static final int BUFFER_WORKSPACE_LENGTH_DIBITS = 25; //This can be adjusted for efficiency + private static final int BUFFER_LENGTH_DIBITS = BUFFER_PROTECTED_REGION_DIBITS + BUFFER_WORKSPACE_LENGTH_DIBITS; + private static final float MAX_POSITIVE_SOFT_SYMBOL = Dibit.D01_PLUS_3.getIdealPhase(); + private static final float MAX_NEGATIVE_SOFT_SYMBOL = Dibit.D11_MINUS_3.getIdealPhase(); + private static final float SYMBOL_QUADRANT_BOUNDARY = (float)(Math.PI / 2.0); + private static final float SYNC_DETECTION_THRESHOLD = 60; + private DMRSyncModeMonitor mSyncModeMonitor = new DMRSyncModeMonitor(); + private DMRSoftSyncDetector mSyncDetector = DMRSoftSyncDetectorFactory.getDetector(); + private DMRSoftSyncDetector mSyncDetectorLag1 = DMRSoftSyncDetectorFactory.getDetector(); + private DMRSoftSyncDetector mSyncDetectorLag2 = DMRSoftSyncDetectorFactory.getDetector(); + private DibitToByteBufferAssembler mDibitAssembler = new DibitToByteBufferAssembler(300); + //Dibit delay line sizing: CACH(12) + MESSAGE_PREFIX(54) + SYNC(24) + private DibitDelayLine mDibitDelayLine = new DibitDelayLine(90); + private DMRMessageFramer mMessageFramer; + private boolean mSyncLock = false; + private float mLaggingSyncOffset1; + private float mLaggingSyncOffset2; + private float mObservedSamplesPerSymbol; + private float mSamplesPerSymbol; + private float mSamplePoint; + private float[] mBuffer; + private int mBufferLoadPointer; + private int mBufferPointer; + private int mBufferWorkspaceLength; + private int mSymbolsSinceLastSync = 0; + + /** + * Constructs an instance + * @param messageFramer to receive demodulated symbols/dibits and sync detect notifications + */ + public DMRSoftSymbolProcessor(DMRMessageFramer messageFramer) + { + if(messageFramer == null) + { + throw new IllegalArgumentException("Message framer cannot be null"); + } + + mMessageFramer = messageFramer; + mSyncModeMonitor.add(mSyncDetector); + mSyncModeMonitor.add(mSyncDetectorLag1); + mSyncModeMonitor.add(mSyncDetectorLag2); + } + + /** + * Sets base station sync detection mode. This disables detection for MOBILE and DIRECT sync patterns. This can + * be used for traffic channels that are known to be part of a base station. + * @param baseStationMode true for base mode only, false otherwise. + */ + public void setBaseStationMode(boolean baseStationMode) + { + if(baseStationMode) + { + mSyncModeMonitor.setMode(DMRSyncDetectMode.BASE_ONLY); + } + } + + /** + * Registers the listener to receive demodulated bit stream buffers. + * @param listener to register + */ + public void setBufferListener(Listener listener) + { + mDibitAssembler.setBufferListener(listener); + } + + /** + * Indicates if there is a registered buffer listener + */ + public boolean hasBufferListener() + { + return mDibitAssembler.hasBufferListeners(); + } + + /** + * Sets the reference timestamp from the incoming sample stream to use for assigning a timestamp to framed messages. + * @param timestamp to set for reference + */ + public void setTimestamp(long timestamp) + { + mMessageFramer.setTimestamp(timestamp); + } + + /** + * Primary input method for receiving a stream of demodulated samples to process into symbols. + * @param samples to process + */ + public void receive(float[] samples) + { + int samplesPointer = 0; + + while(samplesPointer < samples.length) + { + if(mBufferLoadPointer == mBuffer.length) + { + System.arraycopy(mBuffer, mBufferWorkspaceLength, mBuffer, 0, mBuffer.length - mBufferWorkspaceLength); + mBufferLoadPointer -= mBufferWorkspaceLength; + mBufferPointer -= mBufferWorkspaceLength; + } + + int copyLength = Math.min(mBuffer.length - mBufferLoadPointer, samples.length - samplesPointer); + System.arraycopy(samples, samplesPointer, mBuffer, mBufferLoadPointer, copyLength); + samplesPointer += copyLength; + mBufferLoadPointer += copyLength; + + while(mBufferPointer < (mBufferLoadPointer - 7)) //Interpolator needs 1 and optimizer needs 6 pad spaces + { + mBufferPointer++; + mSamplePoint--; + + if(mSamplePoint < 1) + { + mSymbolsSinceLastSync++; + + if(mSymbolsSinceLastSync > 144) + { + mSyncLock = false; + } + + float softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[mBufferPointer], + mBuffer[mBufferPointer + 1], mSamplePoint); + + Dibit symbol = toSymbol(softSymbol); + + //Store the symbol in the delay line and broadcast the delayed ejected symbol to the message framer + //and the bitstream assembler. + Dibit ejected = mDibitDelayLine.insert(symbol); + mMessageFramer.receive(ejected); + mDibitAssembler.receive(ejected); + + float scorePrimary = mSyncDetector.process(softSymbol); + + if(mSyncLock && scorePrimary > SYNC_DETECTION_THRESHOLD && + optimize(mSyncDetector.getDetectedPattern(), 0.0f)) + { + mSymbolsSinceLastSync = 0; + mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern()); + mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern()); + } + else + { + float lag1 = mBufferPointer + mSamplePoint - mLaggingSyncOffset1; + float lag2 = mBufferPointer + mSamplePoint - mLaggingSyncOffset2; + int lagIntegral1 = (int)Math.floor(lag1); + int lagIntegral2 = (int)Math.floor(lag2); + float softSymbolLag1 = PhaseAwareLinearInterpolator.calculate(mBuffer[lagIntegral1], + mBuffer[lagIntegral1 + 1], lag1 - lagIntegral1); + float softSymbolLag2 = PhaseAwareLinearInterpolator.calculate(mBuffer[lagIntegral2], + mBuffer[lagIntegral2 + 1], lag2 - lagIntegral2); + float scoreLag1 = mSyncDetectorLag1.process(softSymbolLag1); + float scoreLag2 = mSyncDetectorLag2.process(softSymbolLag2); + + if(mSymbolsSinceLastSync > 1 && scoreLag1 > scorePrimary && scoreLag1 > scoreLag2 && + scoreLag1 > SYNC_DETECTION_THRESHOLD && + optimize(mSyncDetectorLag1.getDetectedPattern(), -mLaggingSyncOffset1)) + { + mSymbolsSinceLastSync = 0; + mSyncModeMonitor.detected(mSyncDetectorLag1.getDetectedPattern()); + mMessageFramer.syncDetected(mSyncDetectorLag1.getDetectedPattern()); + } + else if(mSymbolsSinceLastSync > 1 && scoreLag2 > scorePrimary && + scoreLag2 > SYNC_DETECTION_THRESHOLD && + optimize(mSyncDetectorLag2.getDetectedPattern(), -mLaggingSyncOffset2)) + { + mSymbolsSinceLastSync = 0; + mSyncModeMonitor.detected(mSyncDetectorLag2.getDetectedPattern()); + mMessageFramer.syncDetected(mSyncDetectorLag2.getDetectedPattern()); + } + else if(scorePrimary > SYNC_DETECTION_THRESHOLD && + optimize(mSyncDetector.getDetectedPattern(), 0.0f)) + { + mSymbolsSinceLastSync = 0; + mSyncLock = true; + mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern()); + mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern()); + } + } + + //Add another symbol's worth of samples to the counter + mSamplePoint += mObservedSamplesPerSymbol; + } + } + } + } + + /** + * Adjusts the symbol timing and symbol spacing to identify the best achievable sync correlation score and apply + * those adjustments when the correlation score exceeds a positive sync detection threshold. + * @param pattern that was detected + * @param additionalOffset from current mBufferPointer and mSamplePoint. This can be zero offset for the primary + * sync detector or an offset for the lagging sync detectors. + * @return true if there is a positive sync detection. + */ + private boolean optimize(DMRSyncPattern pattern, float additionalOffset) + { + //Offset is the start of the first sample of the first symbol of the sync pattern calculated from the current + //buffer pointer and sample point which should be the final sample of the final symbol of the detected sync. + float offset = (mBufferPointer + mSamplePoint) + additionalOffset - (mObservedSamplesPerSymbol * 23); + + //Find the optimal symbol timing + float stepSize = mObservedSamplesPerSymbol / 10.0f; + float stepSizeMin = 0.03f; + float adjustment = 0.0f; + float adjustmentMax = mObservedSamplesPerSymbol / 2.0f; + float candidate = offset; + + int candidateIntegral = (int)Math.floor(candidate); + float candidateFractional = candidate - candidateIntegral; + float scoreCenter = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + + candidate = offset - stepSize; + candidateIntegral = (int)Math.floor(candidate); + candidateFractional = candidate - candidateIntegral; + float scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + + candidate = offset + stepSize; + candidateIntegral = (int)Math.floor(candidate); + candidateFractional = candidate - candidateIntegral; + float scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + + while(stepSize > stepSizeMin && Math.abs(adjustment) <= adjustmentMax) + { + if(scoreLeft > scoreRight && scoreLeft > scoreCenter) + { + adjustment -= stepSize; + scoreRight = scoreCenter; + scoreCenter = scoreLeft; + + candidate = offset + adjustment - stepSize; + candidateIntegral = (int)Math.floor(candidate); + candidateFractional = candidate - candidateIntegral; + scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + } + else if(scoreRight > scoreLeft && scoreRight > scoreCenter) + { + adjustment += stepSize; + scoreLeft = scoreCenter; + scoreCenter = scoreRight; + + candidate = offset + adjustment + stepSize; + candidateIntegral = (int)Math.floor(candidate); + candidateFractional = candidate - candidateIntegral; + scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + } + else + { + stepSize *= 0.5f; + + if(stepSize > stepSizeMin) + { + candidate = offset + adjustment - stepSize; + candidateIntegral = (int)Math.floor(candidate); + candidateFractional = candidate - candidateIntegral; + scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + + candidate = offset + adjustment + stepSize; + candidateIntegral = (int)Math.floor(candidate); + candidateFractional = candidate - candidateIntegral; + scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern); + } + } + } + + //If we didn't find an optimal correlation score above the 95 threshold, return a false sync. + if(scoreCenter < 95) + { + return false; + } + + if(additionalOffset != 0.0) + { + adjustment += additionalOffset; + } + + mSamplePoint += adjustment; + + while(mSamplePoint < 0) + { + mSamplePoint++; + mBufferPointer--; + } + + while(mSamplePoint > 1) + { + mSamplePoint--; + mBufferPointer++; + } + + //Adjust the observed samples per symbol using the timing error measured across one or two bursts when we're in + //fine sync mode and the timing error is not excessive. + if(mSyncLock && adjustment < 0.5 && mSymbolsSinceLastSync > 143 && mSymbolsSinceLastSync < 289) + { + mObservedSamplesPerSymbol += (float)((double)adjustment / (double)mSymbolsSinceLastSync * 0.2); + } + + //If the timing error adjustment is high enough, resample the symbols. Otherwise, overwrite the captured sync + //pattern in the delay buffer with the detected sync pattern to eliminate any sync bit errors. + if(Math.abs(adjustment) > 0.05) + { + float resampleStart = mBufferPointer + mSamplePoint; + resampleStart -= (89 * mObservedSamplesPerSymbol); //Start at 89 (+ 1 current = 90) symbols + int resampleStartIntegral; + + for(int x = 0; x < 66; x++) + { + resampleStartIntegral = (int)Math.floor(resampleStart); + + if(resampleStartIntegral >= 0) + { + float resampledSoftSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[resampleStartIntegral], + mBuffer[resampleStartIntegral + 1], resampleStart - resampleStartIntegral); + mDibitDelayLine.insert(toSymbol(resampledSoftSymbol)); + } + else + { + //This shouldn't happen since there's 2x dibits of padding on the front side, but just in case. + mDibitDelayLine.insert(Dibit.D01_PLUS_3); + } + + resampleStart += mObservedSamplesPerSymbol; + } + + //We don't need to resample the sync region ... just use the actual sync dibit values. + for(Dibit dibit: pattern.toDibits()) + { + mDibitDelayLine.insert(dibit); + } + } + else + { + //Overwrite the most recent 24 dibits with the detected sync so there's no sync bit errors + mDibitDelayLine.update(pattern.toDibits()); + } + + return true; + } + + /** + * Calculates the sync correlation score for sync pattern at the specified offsets and symbol timing. + * @param bufferPointer to the start of the samples in the soft symbol buffer + * @param fractional position to interpolate within the 8 samples starting at the buffer pointer. + * @param samplesPerSymbol spacing to test for. + * @param pattern to correlate against. + * @return correlation score. + */ + public float score(int bufferPointer, float fractional, float samplesPerSymbol, DMRSyncPattern pattern) + { + float score = 0; + float[] symbols = pattern.toSymbols(); + float[] softSymbols = new float[24]; + int integral; + + for(int x = 0; x < 24; x++) + { + float softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional); + softSymbol = Math.min(softSymbol, MAX_POSITIVE_SOFT_SYMBOL); + softSymbol = Math.max(softSymbol, MAX_NEGATIVE_SOFT_SYMBOL); + softSymbols[x] = softSymbol; + score += softSymbol * symbols[x]; + fractional += samplesPerSymbol; + integral = (int)Math.floor(fractional); + bufferPointer += integral; + fractional -= integral; + } + + return score; + } + + /** + * Sets or updates the samples per symbol + * @param samplesPerSymbol to apply. + */ + public void setSamplesPerSymbol(float samplesPerSymbol) + { + mSamplesPerSymbol = samplesPerSymbol; + mObservedSamplesPerSymbol = mSamplesPerSymbol; + mSamplePoint = mSamplesPerSymbol; + mLaggingSyncOffset1 = mSamplesPerSymbol / 3; + mLaggingSyncOffset2 = mLaggingSyncOffset1 * 2; + mBufferWorkspaceLength = (int)Math.ceil(BUFFER_WORKSPACE_LENGTH_DIBITS * mSamplesPerSymbol); + int bufferLength = (int)(Math.ceil(BUFFER_LENGTH_DIBITS * mSamplesPerSymbol)); + mBuffer = new float[bufferLength]; + mBufferLoadPointer = (int)Math.ceil(BUFFER_PROTECTED_REGION_DIBITS * mSamplesPerSymbol); + mBufferPointer = mBufferLoadPointer; + } + + /** + * Decodes the sample value to determine the correct QPSK quadrant and maps the value to a Dibit symbol. + * @param sample in radians. + * @return symbol decision. + */ + private static Dibit toSymbol(float sample) + { + if(sample > 0) + { + return sample > SYMBOL_QUADRANT_BOUNDARY ? Dibit.D01_PLUS_3 : Dibit.D00_PLUS_1; + } + else + { + return sample < -SYMBOL_QUADRANT_BOUNDARY ? Dibit.D11_MINUS_3 : Dibit.D10_MINUS_1; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncDetector.java deleted file mode 100644 index 4bccc8f5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncDetector.java +++ /dev/null @@ -1,178 +0,0 @@ -package io.github.dsheirer.module.decode.dmr; - -import io.github.dsheirer.dsp.symbol.Dibit; -import io.github.dsheirer.dsp.symbol.QPSKCarrierLock; -import org.apache.commons.lang3.Validate; - -/** - * Detector for DMR sync patterns that also includes support for detecting PLL phase lock errors. There are - * two primary use cases for this class: - * - * The first is to detect a sync pattern from a stream of dibits that are processed via the add(dibit) method. After - * each dibit is added, the sync pattern can be inspected by either the hasSync() method or the getSyncPattern() - * method. The maxStreamBitErrors construction parameter specifies how many bit errors can be present when matching - * patterns to the dibit stream. - * - * The second use case is to set a candidate sync pattern value using the setCurrentSyncValue() method. You can then - * use the hasSync() and getSyncPatter() methods to determine if the set value matches a valid sync pattern. The - * maxExplicitBitErrors specifies how many bit errors can be present when matching patterns to the explicitly set - * sync value. - * - * Note: sync pattern matching maximum bit errors uses a hard-coded value for PLL misalignment sync detection since - * this threshold should be relatively high to avoid false triggering and once detected, a PLL should be immediately - * commanded to correct the issue. - */ -public class DMRSyncDetector -{ - private static long SYNC_MASK = 0xFFFFFFFFFFFFl; - private static final int MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT = 1; - private int mMaxStreamBitErrors; - private int mMaxExplicitBitErrors; - private long mCurrentSyncValue; - private long mErrorPattern; - private int mPatternMatchBitErrorCount; - private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN; - private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL; - - /** - * Constructs an instance - * @param maxStreamBitErrors allowed for matching sync patterns when using the dibit streaming use case. - * @param maxExplicitBitErrors allows for matching sync patterns when using the explicitly set sync value use case. - */ - public DMRSyncDetector(int maxStreamBitErrors, int maxExplicitBitErrors) - { - Validate.inclusiveBetween(0, 24, maxStreamBitErrors, - "Max (allowable) stream bit errors for sync match must be between 0 and 24"); - Validate.inclusiveBetween(0, 24, maxExplicitBitErrors, - "Max (allowable) explicit bit errors for sync match must be between 0 and 24"); - mMaxStreamBitErrors = maxStreamBitErrors; - mMaxExplicitBitErrors = maxExplicitBitErrors; - } - - /** - * Sync pattern for the current sync value. - */ - public DMRSyncPattern getSyncPattern() - { - return mSyncPattern; - } - - /** - * Indicates if there is a sync pattern in the currently set sync value that was either explicitly set or was - * aggregated from a stream of dibits. When this method returns true, the sync pattern can be accessed via the - * getSyncPattern() method. - */ - public boolean hasSync() - { - return getSyncPattern() != DMRSyncPattern.UNKNOWN; - } - - /** - * Carrier lock for the QPSK PLL to detect when the PLL has locked to the signal at +/- 90 degrees or 180 degrees. - * @return carrier lock value. - */ - public QPSKCarrierLock getCarrierLock() - { - return mCarrierLock; - } - - /** - * Indicates if the detected sync pattern has normal QPSK carrier lock. - */ - public boolean hasNormalCarrierLock() - { - return getCarrierLock() == QPSKCarrierLock.NORMAL; - } - - /** - * Number of bit errors that were present when matching the current sync value to the current sync pattern. - */ - public int getPatternMatchBitErrorCount() - { - return mPatternMatchBitErrorCount; - } - - /** - * Processes the streaming dibit by left shifting the current sync value and adding the dibit onto the end and then - * test for sync pattern match. - * @param dibit to process and test for sync pattern. - */ - public void add(Dibit dibit) - { - mCurrentSyncValue = Long.rotateLeft(mCurrentSyncValue, 2); - mCurrentSyncValue &= SYNC_MASK; - mCurrentSyncValue += dibit.getValue(); - checkSync(mMaxStreamBitErrors); - } - - /** - * Explicitly sets the argument as the current sync value and tests for sync pattern match. - * @param value to load as the current sync value and test - */ - public void setCurrentSyncValue(long value) - { - mCurrentSyncValue = value; - checkSync(mMaxExplicitBitErrors); - } - - /** - * Checks the current sync value against each of the sync patterns to determine if the value matches a pattern. - */ - private void checkSync(int maxBitErrors) - { - for(DMRSyncPattern pattern: DMRSyncPattern.SYNC_PATTERNS) - { - mErrorPattern = mCurrentSyncValue ^ pattern.getPattern(); - - if(mErrorPattern == 0) - { - mSyncPattern = pattern; - mCarrierLock = QPSKCarrierLock.NORMAL; - mPatternMatchBitErrorCount = 0; - return; - } - - mPatternMatchBitErrorCount = Long.bitCount(mErrorPattern); - - if(mPatternMatchBitErrorCount <= maxBitErrors) - { - mSyncPattern = pattern; - mCarrierLock = QPSKCarrierLock.NORMAL; - return; - } - - - //For PLL mis-aligned lock patterns, reduce the pattern bit error match threshold - mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getPlus90Pattern()); - - if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT) - { - mSyncPattern = pattern; - mCarrierLock = QPSKCarrierLock.PLUS_90; - return; - } - - mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getMinus90Pattern()); - - if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT) - { - mSyncPattern = pattern; - mCarrierLock = QPSKCarrierLock.MINUS_90; - return; - } - - mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getInvertedPattern()); - - if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT) - { - mSyncPattern = pattern; - mCarrierLock = QPSKCarrierLock.INVERTED; - return; - } - } - - mSyncPattern = DMRSyncPattern.UNKNOWN; - mCarrierLock = QPSKCarrierLock.NORMAL; - mPatternMatchBitErrorCount = 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java deleted file mode 100644 index cc7d4e6b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ -package io.github.dsheirer.module.decode.dmr; - -import java.util.EnumSet; - -/** - * DMR burst sync patterns enumeration. In addition to the ETSI defined sync patterns, each enumeration entry also - * contains derived sync patterns that match decoded bit streams where a QPSK Phase-Locked Loop has locked to the signal - * misaligned by +/- 90 degrees, or 180 degrees. These derived patterns can be used to detect PLL misalignment for - * PLL correction purposes. - */ -public enum DMRSyncPattern -{ - BASE_STATION_DATA(0xDFF57D75DF5Dl, 0xBAAFEBEFBAFBl, 0x455014104504l, 0x200A828A20A2l, "BS DATA"), - BASE_STATION_VOICE(0x755FD7DF75F7l, 0xEFFABEBAEFAEl, 0x100541451051l, 0x8AA028208A08l, "BS VOICE A"), - - MOBILE_STATION_DATA(0xD5D7F77FD757l, 0xBFBEAEEABEFEl, 0x404151154101l, 0x2A28088028A8l, "MS DATA"), - MOBILE_STATION_VOICE(0x7F7D5DD57DFDl, 0xEAEBFBBFEBABl, 0x151404401454l, 0x8082A22A8202l, "MS VOICE A"), - DIRECT_MODE_DATA_TIMESLOT_1(0xF7FDD5DDFD55l, 0xAEABBFBBABFFl, 0x515440445400l, 0x08022A2202AAl, "MS DATA - DUAL CAPACITY DIRECT MODE"), - DIRECT_MODE_DATA_TIMESLOT_2(0xD7557F5FF7F5l, 0xBEFFEAFAAEAFl, 0x410015055150l, 0x28AA80A0080Al, "MS DATA - DUAL CAPACITY DIRECT MODE"), - DIRECT_MODE_VOICE_TIMESLOT_1(0x5D577F7757FFl, 0xFBFEEAEEFEAAl, 0x040115110155l, 0xA2A88088A800l, "MS VOICE A - DUAL CAPACITY DIRECT MODE"), - DIRECT_MODE_VOICE_TIMESLOT_2(0x7DFFD5F55D5Fl, 0xEBAABFAFFBFAl, 0x145540500405l, 0x82002A0AA2A0l, "MS VOICE A - DUAL CAPACITY DIRECT MODE"), - MOBILE_STATION_REVERSE_CHANNEL(0x77D55F7DFD77l, 0xEEBFFAEBABEEl, 0x114005145411l, 0x882AA0820288l, "MS RVRS"), - - RESERVED(0xDD7FF5D757DDl, 0xBBEAAFBEFEBBl, 0x441550410144l, 0x22800A28A822l, "RESERVED"), - - //These are used to identify the sync-less sub frames of a voice super frame - BS_VOICE_FRAME_B("BS VOICE B"), - BS_VOICE_FRAME_C("BS VOICE C"), - BS_VOICE_FRAME_D("BS VOICE D"), - BS_VOICE_FRAME_E("BS VOICE E"), - BS_VOICE_FRAME_F("BS VOICE F"), - - MS_VOICE_FRAME_B("MS VOICE B"), - MS_VOICE_FRAME_C("MS VOICE C"), - MS_VOICE_FRAME_D("MS VOICE D"), - MS_VOICE_FRAME_E("MS VOICE E"), - MS_VOICE_FRAME_F("MS VOICE F"), - - UNKNOWN("UNKNOWN"); - - private long mPattern; - private long mPlus90Pattern; - private long mMinus90Pattern; - private long mInvertedPattern; - private String mLabel; - - /** - * DMR Sync Patterns. See TS 102-361-1, paragraph 9.1.1 - */ - DMRSyncPattern(long pattern, long plus90Pattern, long minus90Pattern, long invertedPattern, String label) - { - mPattern = pattern; - mPlus90Pattern = plus90Pattern; - mMinus90Pattern = minus90Pattern; - mInvertedPattern = invertedPattern; - mLabel = label; - } - - /** - * Alternate constructor for syncs that don't have defined patterns. - */ - DMRSyncPattern(String label) - { - mLabel = label; - } - - //Valid sync patterns (excluding the sync-less voice patterns) - public static final EnumSet SYNC_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE, - MOBILE_STATION_DATA, MOBILE_STATION_VOICE, DIRECT_MODE_DATA_TIMESLOT_1, DIRECT_MODE_DATA_TIMESLOT_2, - DIRECT_MODE_VOICE_TIMESLOT_1, DIRECT_MODE_VOICE_TIMESLOT_2, MOBILE_STATION_REVERSE_CHANNEL); - - //Sync patterns containing a Common Announcement Channel (CACH) - private static final EnumSet CACH_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE, - BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F); - - //Direct Mode TS 1 Sync Patterns - private static final EnumSet DIRECT_MODE_TS1_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_1, - DIRECT_MODE_DATA_TIMESLOT_1); - - //Direct Mode TS 2 Sync Patterns - private static final EnumSet DIRECT_MODE_TS2_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_2, - DIRECT_MODE_DATA_TIMESLOT_2); - - //Mobile Station Sync Patterns - private static final EnumSet MOBILE_STATION_PATTERNS = EnumSet.of(MOBILE_STATION_DATA, - MOBILE_STATION_VOICE, MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F); - - /** - * Pattern that represents the enum entry - */ - public long getPattern() - { - return mPattern; - } - - /** - * Pattern that represents the PLL locking to the carrier misaligned by plus 90 degrees. - */ - public long getPlus90Pattern() - { - return mPlus90Pattern; - } - - /** - * Pattern that represents the PLL locking to the carrier misaligned by minus 90 degrees. - */ - public long getMinus90Pattern() - { - return mMinus90Pattern; - } - - /** - * Pattern that represents the PLL locking to the carrier misaligned by 180 degrees (inverted). - */ - public long getInvertedPattern() - { - return mInvertedPattern; - } - - /** - * Indicates if a message using this sync pattern contains a Common Announcement Channel (CACH). - */ - public boolean hasCACH() - { - return CACH_PATTERNS.contains(this); - } - - /** - * Indicates if this is a Direct Mode sync pattern - */ - public boolean isDirectMode() - { - return isDirectModeTS1() || isDirectModeTS2(); - } - - /** - * Indicates if this is Direct Mode sync pattern for timestot 1 - * @return true if DMTS1 - */ - public boolean isDirectModeTS1() - { - return DIRECT_MODE_TS1_PATTERNS.contains(this); - } - - /** - * Indicates if this is Direct Mode sync pattern for timestot 2 - * @return true if DMTS2 - */ - public boolean isDirectModeTS2() - { - return DIRECT_MODE_TS2_PATTERNS.contains(this); - } - - /** - * Indicates if this is a mobile station sync pattern - * @return true if a mobile station sync pattern. - */ - public boolean isMobileStationSyncPattern() - { - return MOBILE_STATION_PATTERNS.contains(this); - } - - /** - * Indicates if this is a mobile sync pattern, either mobile station or direct mode. - * @return true if a mobile sync pattern. - */ - public boolean isMobileSyncPattern() - { - return isMobileStationSyncPattern() || isDirectMode(); - } - - /** - * String representation of the sync pattern - */ - public String toString() - { - return mLabel; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java new file mode 100644 index 00000000..5cc072c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java @@ -0,0 +1,78 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr; + +import io.github.dsheirer.dsp.symbol.Dibit; +import java.util.Arrays; + +/** + * Implements a circular buffer delay line for dibits. + */ +public class DibitDelayLine +{ + private Dibit[] mDelayLine; + public int mPointer; + private int mLength; + + /** + * Constructs an instance + * @param length of the delay line in dibits. + */ + public DibitDelayLine(int length) + { + mLength = length; + mDelayLine = new Dibit[length]; + Arrays.fill(mDelayLine, Dibit.D00_PLUS_1); + } + + /** + * Inserts the dibit into the delay line and returns the oldest dibit from the delay line. + * @param dibit to insert + * @return oldest dibit + */ + public Dibit insert(Dibit dibit) + { + Dibit ejected = mDelayLine[mPointer]; + mDelayLine[mPointer++] = dibit; + mPointer %= mLength; + return ejected; + } + + /** + * Overwrites the most recently added dibits in the delay line with the provided sequence. This can be used when + * a sync pattern is detected to overwrite the calculated symbols with the known sync pattern. + * @param dibits to overwrite + */ + public void update(Dibit[] dibits) + { + mPointer = mPointer - dibits.length; + + if(mPointer < 0) + { + mPointer += mDelayLine.length; + } + + for(Dibit dibit: dibits) + { + mDelayLine[mPointer++] = dibit; + mPointer %= mLength; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/IDMRBurstDetectListener.java b/src/main/java/io/github/dsheirer/module/decode/dmr/IDMRBurstDetectListener.java deleted file mode 100644 index c9d8d17b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/IDMRBurstDetectListener.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer, Zhenyu Mao - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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.dmr; - - -import io.github.dsheirer.bits.CorrectedBinaryMessage; - -/** - * Listener interface to be notified each time a DMR burst has been detected or when sync is or continues to be lost. - */ -public interface IDMRBurstDetectListener -{ - /** - * Indicates that a DMR sync has been detected and binary message with the burst contents are ready for processing - * @param binaryMessage burst Binary Message and any bit errors detected in the message - * @param pattern SyncPattern - */ - void burstDetected(CorrectedBinaryMessage binaryMessage, DMRSyncPattern pattern, int timeslot); - - /** - * Indicates that sync has been lost on the dibit stream - * - * @param bitsProcessed since the last sync detect - * @param timeslot that was being tracked when the sync loss was detected - */ - void syncLost(int bitsProcessed, int timeslot); -} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/TimeslotAlignmentTracker.java b/src/main/java/io/github/dsheirer/module/decode/dmr/TimeslotAlignmentTracker.java deleted file mode 100644 index 87094fdf..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/TimeslotAlignmentTracker.java +++ /dev/null @@ -1,155 +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.module.decode.dmr; - -import io.github.dsheirer.module.decode.dmr.message.CACH; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Tracks proper alignment of timeslots into the two bursts of a burst framer. - */ -public class TimeslotAlignmentTracker -{ - private final static Logger mLog = LoggerFactory.getLogger(TimeslotAlignmentTracker.class); - - private int mBurst1Ts1 = 0; - private int mBurst1Ts2 = 0; - private int mBurst2Ts1 = 0; - private int mBurst2Ts2 = 0; - - /** - * Indicates if the track has accumulated enough cach data to accurately declare timeslot alignment. The test - * checks that both timeslots each have different counts for timeslot 0 and 1 and that both timeslots each indicate - * a different dominant timeslot than the other. - */ - public boolean hasSufficientData() - { - return ((mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) || - (mBurst1Ts1 < mBurst1Ts2 && mBurst2Ts2 < mBurst2Ts1) || - (mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts1 != mBurst2Ts2) || - (mBurst1Ts1 != mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst1Ts2 == 0)); - } - - /** - * Indicates if the timeslots are aligned by inspection of the CACH value for frame 0 and frame 1 - */ - public boolean isAligned() - { - return (mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) || - (mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts2 > mBurst2Ts1) || - (mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst2Ts2 == 0); - } - - /** - * Updates the tracker with the cach fields or sync patterns from the current TDMA burst frames - * @param cach1 optional cach that is currently aligned as timeslot 1 - * @param cach2 optional cach that is currently aligned as timeslot 2 - * @param pattern1 sync pattern for the burst currently aligned as timeslot 1 - * @param pattern2 sync pattern for the burst currently aligned as timeslot 2 - */ - public void update(CACH cach1, CACH cach2, DMRSyncPattern pattern1, DMRSyncPattern pattern2) - { - if(cach1 != null && cach1.isValid()) - { - if(cach1.isTimeslot0()) - { - mBurst1Ts1++; - } - else - { - mBurst1Ts2++; - } - } - - if(cach2 != null && cach2.isValid()) - { - if(cach2.isTimeslot0()) - { - mBurst2Ts1++; - } - else - { - mBurst2Ts2++; - } - } - - if(pattern1.isDirectMode()) - { - switch((pattern1)) - { - case DIRECT_MODE_DATA_TIMESLOT_1: - case DIRECT_MODE_VOICE_TIMESLOT_1: - mBurst1Ts1++; - break; - case DIRECT_MODE_DATA_TIMESLOT_2: - case DIRECT_MODE_VOICE_TIMESLOT_2: - mBurst1Ts2++; - break; - } - } - - if(pattern2.isDirectMode()) - { - switch((pattern2)) - { - case DIRECT_MODE_DATA_TIMESLOT_1: - case DIRECT_MODE_VOICE_TIMESLOT_1: - mBurst2Ts1++; - break; - case DIRECT_MODE_DATA_TIMESLOT_2: - case DIRECT_MODE_VOICE_TIMESLOT_2: - mBurst2Ts2++; - break; - } - } - } - - /** - * Resets the tracker values. - */ - public void reset() - { - mBurst1Ts1 = 0; - mBurst1Ts2 = 0; - mBurst2Ts1 = 0; - mBurst2Ts2 = 0; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("BURST 1: ").append(mBurst1Ts1).append("/").append(mBurst1Ts2); - sb.append(" BURST 2:").append(mBurst2Ts1).append("/").append(mBurst2Ts2); - if(hasSufficientData()) - { - if(isAligned()) - { - sb.append(" **ALIGNED**"); - } - else - { - sb.append(" >>MIS-ALIGNED<<"); - } - } - return sb.toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java index 69177173..eb503656 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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,13 +33,14 @@ import io.github.dsheirer.identifier.tone.ToneSequence; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageProvider; import io.github.dsheirer.module.decode.dmr.identifier.DMRToneIdentifier; +import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader; -import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser; -import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; +import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; @@ -98,7 +99,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP } /** - * Processes DMR AMBE audio frame + * Processes DMR AMBE audio frames and signalling messages. */ public void receive(IMessage message) { @@ -108,49 +109,69 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP if(!mEncryptedCallStateEstablished) { //DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted. - if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirectMode()) + if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirect()) { mEncryptedCallStateEstablished = true; mEncryptedCall = false; } //Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames //in the EMB field. - else if(message instanceof VoiceEMBMessage voiceMessage && voiceMessage.getEMB().isValid()) + else if(message instanceof VoiceEMBMessage voice) + { + if(voice.hasEmbeddedParameters() && + voice.getEmbeddedParameters().getShortBurst() instanceof EmbeddedEncryptionParameters) + { + mEncryptedCallStateEstablished = true; + mEncryptedCall = true; + } + else if(voice.getEMB().isValid()) + { + mEncryptedCallStateEstablished = true; + mEncryptedCall = voice.getEMB().isEncrypted(); + } + } + else if(message instanceof VoiceHeader vh && vh.getLCMessage() instanceof AbstractVoiceChannelUser vcu && + vcu.isValid()) { mEncryptedCallStateEstablished = true; - mEncryptedCall = voiceMessage.getEMB().isEncrypted(); + mEncryptedCall = vcu.getServiceOptions().isEncrypted(); } - else if(message instanceof VoiceHeader voiceHeader) + else if(message instanceof PiHeader pi && pi.getLCMessage() instanceof EncryptionParameters ep && + ep.isValid()) { - LCMessage lc = voiceHeader.getLCMessage(); - - if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well - { - if(flc.isValid()) - { - mEncryptedCallStateEstablished = true; - mEncryptedCall = flc.isEncrypted(); - } - } + mEncryptedCallStateEstablished = true; + mEncryptedCall = true; } //Note: the DMRMessageProcessor extracts Full Link Control messages from Voice Frames B-C and sends them // independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of // an ongoing call and can be used to establish encryption state when the FLC is a voice channel user. - else if(message instanceof AbstractVoiceChannelUser avcu) + else if(message instanceof AbstractVoiceChannelUser avcu && avcu.isValid()) { mEncryptedCallStateEstablished = true; mEncryptedCall = avcu.getServiceOptions().isEncrypted(); } + + if(mEncryptedCall) + { + mQueuedAmbeFrames.clear(); + } } //Queue or process audio frames. Note: audio frames are held/queued until encrypted state is established //before any audio is generated for the audio segment. if(message instanceof VoiceMessage voiceMessage) { - List frames = voiceMessage.getAMBEFrames(); - for(byte[] frame: frames) + if(mEncryptedCallStateEstablished && mEncryptedCall) { - processAudio(frame, message.getTimestamp()); + mQueuedAmbeFrames.clear(); + } + else + { + List frames = voiceMessage.getAMBEFrames(); + for(byte[] frame: frames) + { + processAudio(frame, message.getTimestamp()); + } } } else if(message instanceof Terminator) @@ -168,23 +189,30 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP { if(mEncryptedCallStateEstablished) { - //Process any ambe frames that were queued awaiting encryption state determination - if(!mQueuedAmbeFrames.isEmpty()) + if(mEncryptedCall) { - if(!mEncryptedCall) + mQueuedAmbeFrames.clear(); + } + else + { + //Process any ambe frames that were queued awaiting encryption state determination + if(!mQueuedAmbeFrames.isEmpty()) { - for(byte[] queuedFrame: mQueuedAmbeFrames) + if(!mEncryptedCall) { - produceAudio(queuedFrame, timestamp); + for(byte[] queuedFrame: mQueuedAmbeFrames) + { + produceAudio(queuedFrame, timestamp); + } + + mQueuedAmbeFrames.clear(); } mQueuedAmbeFrames.clear(); } - mQueuedAmbeFrames.clear(); + produceAudio(frame, timestamp); } - - produceAudio(frame, timestamp); } else { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java new file mode 100644 index 00000000..fd111a7d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java @@ -0,0 +1,360 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.bptc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.IHamming; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Block Product Turbo Code (BPTC) base implementation for BPTC codes using simple even parity column checks. + */ +public abstract class BPTCBase +{ + public static int ERRORS_NOT_CORRECTED = -2; + private IHamming mHamming; + private int mColumnCount; + private int mRowCount; + + /** + * Constructs an instance + * @param hamming to provide row checking using the correct Hamming error detection and correction algorithm. + * @param column count + * @param row count + */ + public BPTCBase(IHamming hamming, int columnCount, int rowCount) + { + mHamming = hamming; + mColumnCount = columnCount; + mRowCount = rowCount; + } + + /** + * Performs error detection and correction. + * @param message to correct + * @return true if the message was corrected or false if there are uncorrectable errors. + */ + public boolean correct(CorrectedBinaryMessage message) + { + BinaryMessage columns = getColumnErrors(message); + BinaryMessage rows = getRowErrors(message); + + if(columns.isEmpty() && rows.isEmpty()) + { + return true; + } + + List correctedIndexes = new ArrayList<>(); + + //1: fix non-shadowed single bit errors in each row + for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1)) + { + int index = correct1BitErrors(row, message, rows); + + if(index >= 0) + { + correctedIndexes.add(index); + } + } + + //Refresh the column errors + columns = getColumnErrors(message); + + if(columns.isEmpty() && rows.isEmpty()) + { + message.incrementCorrectedBitCount(correctedIndexes.size()); + return true; + } + + //2: fix non-shadowed multi bit errors (up to 2) in each row + if(columns.cardinality() <= 2) + { + for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1)) + { + List solution = correctMultiBitErrors(row, message, columns, rows); + + if(solution.size() > 0) + { + correctedIndexes.addAll(solution); + } + } + } + + if(columns.isEmpty() && rows.isEmpty()) + { + message.incrementCorrectedBitCount(correctedIndexes.size()); + return true; + } + + message.setCorrectedBitCount(ERRORS_NOT_CORRECTED); + return false; + } + + /** + * Indicates if the column passes an even parity check. + * @param column to test + * @return true if the parity check passes + */ + public boolean isColumnCorrect(int column, BinaryMessage message) + { + boolean correct = true; + + for(int row = 0; row < mRowCount; row++) + { + correct ^= message.get(row * mColumnCount + column); + } + + return correct; + } + + /** + * Calculates the column from the bit index/offset + * @param index for a bit + * @return column that contains the index + */ + public int getColumn(int index) + { + return index % mColumnCount; + } + + /** + * Calculates the index for a bit that exists at the row and column. + * @param column for the bit. + * @param row for the bit + * @return index for the bit. + */ + public int getIndex(int column, int row) + { + return row * mColumnCount + column; + } + + /** + * Checks the row for bit errors. + * @param row to check + * @param message containing bits to check + * @return true if the row passes the Hamming check or false if not. + */ + private boolean isRowCorrect(int row, CorrectedBinaryMessage message) + { + return mHamming.getErrorIndex(message, row * mColumnCount) == IHamming.NO_ERRORS; + } + + /** + * Calculates the index for the bit error in the message + * @param row to check + * @param message with bits + * @return error index in the range 0-195, or -1 if the message row has no bit errors. + */ + private int getRowErrorIndex(int row, CorrectedBinaryMessage message) + { + return mHamming.getErrorIndex(message, row * mColumnCount); + } + + /** + * Returns a bit map indicating the row errors. + * @param message containing rows. + * @return row error map + */ + public BinaryMessage getRowErrors(CorrectedBinaryMessage message) + { + BinaryMessage rows = new BinaryMessage(mRowCount); + + for(int row = 0; row < mRowCount; row++) + { + rows.set(row, !isRowCorrect(row, message)); + } + + return rows; + } + + /** + * Returns a bit map indicating the column errors. + * @param message containing rows. + * @return column error map + */ + public BinaryMessage getColumnErrors(CorrectedBinaryMessage message) + { + BinaryMessage columns = new BinaryMessage(mColumnCount); + + for(int column = 0; column < mColumnCount; column++) + { + columns.set(column, !isColumnCorrect(column, message)); + } + + return columns; + } + + /** + * Corrects single bit errors in each row using only the Hamming error index. + * @param row to correct + * @param message with bits + * @param rows error flag set + * @return bit index that was corrected or -2 if the error was not corrected + */ + public int correct1BitErrors(int row, CorrectedBinaryMessage message, BinaryMessage rows) + { + int index = getRowErrorIndex(row, message); + + if(index >= 0 && index < IHamming.MULTIPLE_ERRORS) + { + message.flip(index); + + if(isRowCorrect(row, message)) + { + rows.clear(row); + return index; + } + + message.flip(index); + } + + return ERRORS_NOT_CORRECTED; + } + + /** + * Corrects multiple bit errors in a single row when all other rows are corrected first, by flipping the + * bits in the row that show errors in the columns and testing the solution. + * @param row to correct + * @param message with bits + * @param columns bit error matrix + * @param rows bit error matrix + * @return solution or an empty list if no solution. + */ + public List correctMultiBitErrors(int row, CorrectedBinaryMessage message, BinaryMessage columns, + BinaryMessage rows) + { + //Test flipping bits in the row everywhere that we have a column error + List testFlips = new ArrayList<>(); + + for(int column = columns.nextSetBit(0); column >= 0 && column < mColumnCount; + column = columns.nextSetBit(column + 1)) + { + int toFlip = getIndex(column, row); + message.flip(toFlip); + testFlips.add(toFlip); + } + + if(!isRowCorrect(row, message)) + { + int index = getRowErrorIndex(row, message); + + if(index >= 0 && index < IHamming.MULTIPLE_ERRORS) + { + message.flip(index); + + if(testFlips.contains(index)) + { + testFlips.removeIf(value -> value == index); + } + else + { + testFlips.add(index); + } + } + } + + if(isRowCorrect(row, message)) + { + //Check the columns also + + boolean columnsCorrect = true; + + for(Integer flip: testFlips) + { + columnsCorrect &= isColumnCorrect(getColumn(flip), message); + } + + if(columnsCorrect) + { + rows.clear(row); + + for(Integer flip: testFlips) + { + columns.clear(getColumn(flip)); + } + + return testFlips; + } + } + + for(Integer flip: testFlips) + { + message.flip(flip); + } + + return Collections.emptyList(); + } + + public void logErrorMap(CorrectedBinaryMessage message) + { + StringBuilder sb = new StringBuilder(); + for(int row = 0; row < mRowCount; row++) + { + int offset = row * mColumnCount; + sb.append("Row ").append(row).append(": "); + sb.append(message.getSubMessage(offset, offset + mColumnCount)); + sb.append(" ").append(offset).append(":").append(offset + mColumnCount).append("\n"); + } + + System.out.println(sb); + } + + public void logDiagnostic(CorrectedBinaryMessage message) + { + StringBuilder sb = new StringBuilder(); + for(int row = 0; row < mRowCount; row++) + { + int offset = row * mColumnCount; + sb.append(" Row ").append(row).append(": "); + sb.append(message.getSubMessage(offset, offset + mColumnCount)); + int index = getRowErrorIndex(row, message); + if(index != IHamming.NO_ERRORS) + { + sb.append(" < Index:").append(index); + + if(index == IHamming.MULTIPLE_ERRORS) + { + sb.append(" (2+ bit errors)"); + } + } + sb.append("\n"); + } + + sb.append("Errors: "); + + BinaryMessage columnErrors = getColumnErrors(message); + for(int column = 0; column < mColumnCount; column++) + { + if(columnErrors.get(column)) + { + sb.append("^"); + } + else + { + sb.append("."); + } + } + + System.out.println("Diagnostic:\n" + sb + "\n"); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java new file mode 100644 index 00000000..274a70c0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java @@ -0,0 +1,132 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.bptc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.Hamming16; + +/** + * Block product turbo code (BPTC) for decoding Full Link Control messages reassembled from the 32-bit payload fragments + * in each of the Voice B-E messages. + */ +public class BPTC_128_77 extends BPTCBase +{ + public BPTC_128_77() + { + super(new Hamming16(), 16, 8); + } + + /** + * Performs error detection and correction and extracts the payload from the BPTC encoded message. + * @param message with BPTC encoding. + * @return decoded message. + */ + public CorrectedBinaryMessage extract(BinaryMessage message) + { + CorrectedBinaryMessage deinterleaved = deinterleave(message); + correct(deinterleaved); + + CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77); + int pointer = 0; + + //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits. + for(int row = 0; row < 2; row++) + { + for(int column = 0; column < 11; column++) + { + extractedMessage.set(pointer++, deinterleaved.get(row * 16 + column)); + } + } + + for(int row = 2; row < 7; row++) + { + for(int column = 0; column < 10; column++) + { + extractedMessage.set(pointer++, deinterleaved.get(row * 16 + column)); + } + + //Transfer the checksum parity bit from this row + extractedMessage.set(70 + row, deinterleaved.get(row * 16 + 10)); + } + + extractedMessage.setCorrectedBitCount(deinterleaved.getCorrectedBitCount()); + return extractedMessage; + } + + /** + * Deinterleaves the transmitted message. + * @param interleaved message + * @return deinterleaved message. + */ + public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved) + { + CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(128); + int index; + deinterleaved.set(127, interleaved.get(127)); + for(int i = 0; i < 127; i++) + { + index = (i * 8) % 127; + deinterleaved.set(i, interleaved.get(index)); + } + + return deinterleaved; + } + + public static void main(String[] args) + { + BPTC_128_77 bptc = new BPTC_128_77(); + String deinterleavedRaw = "00000000000000000000000000000000001000000010010101011001001000010000000000000000000000000110101000010110111100010000111110011111"; + + String deinterleavedReference = "00000000000000000000000000000000000000000010011100011001001000110000000000000000000000000110101000010110111100010000111110011111"; //No errors + CorrectedBinaryMessage deinterleavedReferenceMessage = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedReference)); + + String interleaved = "00000000000000000000000000010010000100010000001000000010000100010000001100000110001101100000001100000101001000010010010100110011"; //Under test + CorrectedBinaryMessage interleavedMessage = new CorrectedBinaryMessage(BinaryMessage.load(interleaved)); +// CorrectedBinaryMessage deinterleaved = deinterleave(interleavedMessage); + CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedRaw)); + + + String deinterleavedUncorrected = deinterleaved.toString(); + bptc.logDiagnostic(deinterleaved); + bptc.correct(deinterleaved); + + System.out.println("DEINTERLEAVED: " + deinterleavedUncorrected); + System.out.println(" CORRECTED: " + deinterleaved); + System.out.println(" REFERENCE: " + deinterleavedReferenceMessage); + + deinterleaved.xor(deinterleavedReferenceMessage); + System.out.println(" DELTA: " + deinterleaved); + + if(deinterleaved.cardinality() > 0) + { + System.out.println("--------------------------------"); + System.out.println("Residual Error Map:"); + bptc.logErrorMap(deinterleaved); + System.out.println("--------------------------------"); + } + else + { + System.out.println("--------------------------------"); + System.out.println("Message Was Fully Corrected"); + System.out.println("--------------------------------"); + } + } +} diff --git a/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_16_2.java similarity index 96% rename from src/main/java/io/github/dsheirer/edac/BPTC_16_2.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_16_2.java index 9840e1c6..3bae9c2d 100644 --- a/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_16_2.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,11 @@ * **************************************************************************** */ -package io.github.dsheirer.edac; +package io.github.dsheirer.module.decode.dmr.bptc; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.Hamming16; /** * Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field. diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_196_96.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_196_96.java new file mode 100644 index 00000000..3fcc011a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_196_96.java @@ -0,0 +1,670 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.bptc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.BitSetFullException; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.Hamming13; +import io.github.dsheirer.edac.Hamming15; +import io.github.dsheirer.edac.IHamming; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Block Product Turbo Code (BPTC) 196/96 Support + */ +public class BPTC_196_96 +{ + public static final int BPTC_LENGTH = 196; + public static final int EXTRACTED_LENGTH = 96; //However, we set the 3x reserved bits in 96, 97, and 98 making the length 99 + public static final int MAX_ORIGINAL_INDEX = 136; + public static final int COLUMN_COUNT = 15; + public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit + public static final int CHECKSUM_COLUMN_COUNT = 4; + public static final int MESSAGE_START_INDEX = 4; + public static final int MAXIMUM_RECURSION_TURBO_DEPTH = 20; + + /** + * DMR De-interleaving indices + */ + public static int[] BPTC_DEINTERLEAVE = new int[]{0, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 16, 1, 182, + 167, 152, 137, 122, 107, 92, 77, 62, 47, 32, 17, 2, 183, 168, 153, 138, 123, 108, 93, 78, 63, 48, 33, 18, 3, + 184, 169, 154, 139, 124, 109, 94, 79, 64, 49, 34, 19, 4, 185, 170, 155, 140, 125, 110, 95, 80, 65, 50, 35, 20, + 5, 186, 171, 156, 141, 126, 111, 96, 81, 66, 51, 36, 21, 6, 187, 172, 157, 142, 127, 112, 97, 82, 67, 52, 37, + 22, 7, 188, 173, 158, 143, 128, 113, 98, 83, 68, 53, 38, 23, 8, 189, 174, 159, 144, 129, 114, 99, 84, 69, 54, + 39, 24, 9, 190, 175, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 191, 176, 161, 146, 131, 116, 101, 86, 71, + 56, 41, 26, 11, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 193, 178, 163, 148, 133, 118, 103, + 88, 73, 58, 43, 28, 13, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 195, 180, 165, 150, 135, 120, + 105, 90, 75, 60, 45, 30, 15}; + + /** + * DMR Column Indices + */ + public static int[][] COLUMN_INDEXES = new int[][]{ + {1, 16, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166, 181}, + {2, 17, 32, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182}, + {3, 18, 33, 48, 63, 78, 93, 108, 123, 138, 153, 168, 183}, + {4, 19, 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184}, + {5, 20, 35, 50, 65, 80, 95, 110, 125, 140, 155, 170, 185}, + {6, 21, 36, 51, 66, 81, 96, 111, 126, 141, 156, 171, 186}, + {7, 22, 37, 52, 67, 82, 97, 112, 127, 142, 157, 172, 187}, + {8, 23, 38, 53, 68, 83, 98, 113, 128, 143, 158, 173, 188}, + {9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 159, 174, 189}, + {10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 175, 190}, + {11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 191}, + {12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192}, + {13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193}, + {14, 29, 44, 59, 74, 89, 104, 119, 134, 149, 164, 179, 194}, + {15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195}}; + + /** + * Performs de-interleave, error detect and correct and extracts a 96-bit message from the DMR BPTC(196,96) + * protected message + * @param original uncorrected BPTC(196,96) message + * @return error corrected and extracted 96-bit message + */ + public static CorrectedBinaryMessage extract(CorrectedBinaryMessage original) + { + //De-Interleave + CorrectedBinaryMessage message = deinterleave(original); + + //Perform error detection and correction + boolean pursueShadows = true; + correct(message, pursueShadows); + + //Extract the 96-bit message from the corrected 196 bits + CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(EXTRACTED_LENGTH); + + //Transfer the corrected bit count + extracted.setCorrectedBitCount(message.getCorrectedBitCount()); + + int index = MESSAGE_START_INDEX; + + while(index < MAX_ORIGINAL_INDEX) + { + if((index % COLUMN_COUNT) < MESSAGE_COLUMN_COUNT) + { + try + { + extracted.add(message.get(index)); + } + catch(BitSetFullException bsfe) + { + //Should never get to here + bsfe.printStackTrace(); + return extracted; + } + + index++; + } + else + { + index += CHECKSUM_COLUMN_COUNT; + } + } + + //Transfer bits R2, R1, and R0 to the end - RAS bits + extracted.set(96, message.get(0)); + extracted.set(97, message.get(1)); + extracted.set(98, message.get(2)); + + return extracted; + } + + /** + * De-interleaves the transmitted bits of a DMR BPTC(196,96) protected message + * @param message that contains interleaved (transmitted) message + * @return de-interleaved message + */ + private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage message) + { + CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BPTC_LENGTH); + for(int x = 0; x < BPTC_DEINTERLEAVE.length; x++) + { + deinterleaved.set(x, message.get(BPTC_DEINTERLEAVE[x])); + } + + return deinterleaved; + } + + /** + * Identifies the error indices from the row level Hamming(15,11,3) protected words + * @param message containing protected words in rows + * @return list of zero or more error indices + */ + private static List getRowErrors(CorrectedBinaryMessage message) + { + List rowErrors = new ArrayList<>(); + + int offset = 0; + int index = -1; + + for(int row = 0; row < 13; row++) + { + //The first bit of the message is a pad/reserved bit and is ignored. + offset = (row * 15) + 1; + index = Hamming15.getErrorIndex(message, offset); + + if(index >= 0 && index < 1000) + { + rowErrors.add(index); + } + } + + //Note: row errors should already be in order and don't need sorted + return rowErrors; + } + + /** + * Identifies the error indices from the column level Hamming(13,9,3) protected words + * @param message containing protected words in columns + * @return list of zero or more error indices + */ + private static List getColumnErrors(CorrectedBinaryMessage message) + { + List columnErrors = new ArrayList<>(); + + int index = -1; + + for(int column = 0; column < 15; column++) + { + index = Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]); + + if(index >= 0 && index < 1000) + { + columnErrors.add(index); + } + } + + Collections.sort(columnErrors); + return columnErrors; + } + + /** + * Creates a list of the values that intersect between list 1 and list 2. + * @param list1 to compare + * @param list2 to compare + * @return intersection + */ + private static List intersection(List list1, List list2) + { + List intersection = new ArrayList<>(); + + for(Integer i : list1) + { + if(list2.contains(i)) + { + intersection.add(i); + } + } + + return intersection; + } + + private static void logDiagnostic(CorrectedBinaryMessage message) + { + StringBuilder sb = new StringBuilder(); + for(int row = 0; row < 13; row++) + { + int offset = (row * 15) + 1; + sb.append("Row "); + if(row < 10) + { + sb.append(" ").append(row); + } + else + { + sb.append(row); + } + sb.append(": "); + sb.append(message.getSubMessage(offset, offset + 16)); + int index = getRowErrorIndex(row, message); + sb.append(" Index:").append(index == IHamming.NO_ERRORS ? "-": index); + sb.append("\n"); + } + + sb.append("Column Error Indices: "); + for(int column = 0; column < 15; column++) + { + int index = getColumnErrorIndex(column, message); + String label = index == IHamming.NO_ERRORS ? "-" : (index == 1000 ? "(bad)" : String.valueOf(index)); + sb.append(column).append(":").append(label).append(" "); + } + + System.out.println("Diagnostic:\n" + sb + "\n"); + } + + private static void logErrorMap(CorrectedBinaryMessage message) + { + StringBuilder sb = new StringBuilder(); + for(int row = 0; row < 13; row++) + { + int offset = (row * 15) + 1; + sb.append("Row "); + if(row < 10) + { + sb.append(" ").append(row); + } + else + { + sb.append(row); + } + sb.append(": "); + sb.append(message.getSubMessage(offset, offset + 15)); + + sb.append(" ").append(offset).append(":").append(offset + 14).append(" ="); + + for(int index = offset; index < offset + 15; index++) + { + if(message.get(index)) + { + sb.append(" " + index); + } + } + sb.append("\n"); + } + + System.out.println("Error Map:\n" + sb + "\n"); + } + + /** + * Calculates the indices that make up the intersections of the row and column errors. + * @param rows bitmap indicating rows with errors + * @param columns bitmap indicating columns with errors + * @return set of intersection indices. + */ + public static List getIntersectionIndices(BinaryMessage columns, BinaryMessage rows) + { + List intersections = new ArrayList<>(); + + for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1)) + { + for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1)) + { + intersections.add(getIndex(column, row)); + } + } + + return intersections; + } + + /** + * Performs turbo decode of the message by testing multiple paths across rows and columns to identify paths + * that resolve errors validated by the Hamming checks. Can also detect and often correct shadowing where the bit + * errors occur in a 2x2, 2x3, 3x2 or 3x3 column/row grid and can't normally be corrected by the Hamming code. + * @param message to correct. + * @param pursueShadows to test intersection flipping for double/triple shadows. + * @return true if the message was corrected, false otherwise. + */ + public static boolean correct(CorrectedBinaryMessage message, boolean pursueShadows) + { + List columnErrors = getColumnErrors(message); + List rowErrors = getRowErrors(message); + + if(columnErrors.isEmpty() && rowErrors.isEmpty()) + { + return true; + } + + //Fix the easy bits first. + List flippedBits = new ArrayList<>(); + List intersection = intersection(columnErrors, rowErrors); + + if(!intersection.isEmpty()) + { + for(Integer i : intersection) + { + message.flip(i); + flippedBits.add(i); + } + + message.incrementCorrectedBitCount(intersection.size()); + } + + //Set of flags for columns with errors + BinaryMessage columns = new BinaryMessage(15); + for(int column = 0; column < 15; column++) + { + columns.set(column, !isColumnCorrect(column, message)); + } + + //Set of flags for rows with errors + BinaryMessage rows = new BinaryMessage(13); + for(int row = 0; row < 13; row++) + { + rows.set(row, !isRowCorrect(row, message)); + } + + if(columns.isEmpty() && rows.isEmpty()) + { + return true; //No (more) errors + } + + //1: fix up to 2-bit errors per column or row without pursuing multi-column or multi-row paths + boolean pursue = false; + + for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1)) + { + List solution = correctColumn(column, message, columns, rows, 0, pursue, new ArrayList<>()); + flippedBits.addAll(solution); + } + + if(!columns.isEmpty() || !rows.isEmpty()) + { + for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1)) + { + List solution = correctRow(row, message, columns, rows, 0, pursue, new ArrayList<>()); + flippedBits.addAll(solution); + } + } + + if(!columns.isEmpty() || !rows.isEmpty()) + { + pursue = true; + + for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1)) + { + List solution = correctColumn(column, message, columns, rows, 0, pursue, new ArrayList<>()); + flippedBits.addAll(solution); + } + + if(!columns.isEmpty() || !rows.isEmpty()) + { + for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1)) + { + List solution = correctRow(row, message, columns, rows, 0, pursue, new ArrayList<>()); + flippedBits.addAll(solution); + } + } + } + + //Detect double/triple row/column bit errors that are shadowing each other and preventing the + // Hamming codes from giving accurate bit error locations. Flip all of their intersections and + // then attempt to correct the message again. We use a flag 'pursueShadows' to ensure that we + // only chase the shadow once, not recursively, since this parent method can be called recursively. + if(pursueShadows && ((columns.cardinality() == 2 || columns.cardinality() == 3) && + (rows.cardinality() == 2 || rows.cardinality() == 3))) + { + List intersectionIndices = getIntersectionIndices(columns, rows); + for(Integer intersectionIndex: intersectionIndices) + { + message.flip(intersectionIndex); + } + + //Set pursue shadows to false so we don't recursively chase shadows + pursueShadows = false; + + if(correct(message, pursueShadows)) + { + columns.clear(); + rows.clear(); + int correctedBits = intersectionIndices.size() - message.getCorrectedBitCount(); + message.incrementCorrectedBitCount(correctedBits); + } + else + { + for(Integer intersectionIndex: intersectionIndices) + { + message.flip(intersectionIndex); + } + } + } + + if(columns.isEmpty() && rows.isEmpty()) + { + Collections.sort(flippedBits); + message.incrementCorrectedBitCount(flippedBits.size()); + } + else + { + message.setCorrectedBitCount(BPTCBase.ERRORS_NOT_CORRECTED); + return false; + } + + return true; + } + + /** + * Recursive method ... + * @param column + * @param message + * @param columns + * @param rows + * @return + */ + public static List correctColumn(int column, CorrectedBinaryMessage message, BinaryMessage columns, + BinaryMessage rows, int depth, boolean pursue, List path) + { + //Don't let the recursive call exceed threshold + if(depth > MAXIMUM_RECURSION_TURBO_DEPTH) + { + return Collections.emptyList(); + } + + int index = getColumnErrorIndex(column, message); + int row = getRow(index); + + //If the candidate index is in range and the row for the candidate has an error, proceed + if(0 <= index && index <= 195 && rows.get(row) && !path.contains(index)) + { + message.flip(index); + + if(isColumnCorrect(column, message)) + { + path.add(index); + + //If the row is now correct, stop here. + if(isRowCorrect(row, message)) + { + columns.clear(column); + rows.clear(row); + return path; + } + + //Recursively test bit flipping in the row. A non-empty list is the solution set. + List solution = correctRow(row, message, columns, rows, depth + 1, pursue, path); + + //If our candidate index is part of the discovered solution, fail, otherwise return it as part of + // the solution. + if(solution.size() > 0) + { + columns.clear(column); + return solution; + } + else if(pursue) //Pursue additional column flips + { + for(int column2 = columns.nextSetBit(column + 1); column2 >= 0 && column2 < 15; column2 = columns.nextSetBit(column2 + 1)) + { + List solution2 = correctColumn(column2, message, columns, rows, depth + 1, pursue, path); + + if(solution2.size() > 0) + { + columns.clear(column); + rows.clear(row); + return solution2; + } + } + } + + path.removeLast(); + } + + //Revert the change + message.flip(index); + } + + return Collections.emptyList(); + } + + public static List correctRow(int row, CorrectedBinaryMessage message, BinaryMessage columns, + BinaryMessage rows, int depth, boolean pursue, List path) + { + //Don't let the recursive call exceed threshold + if(depth > MAXIMUM_RECURSION_TURBO_DEPTH) + { + return Collections.emptyList(); + } + + int index = getRowErrorIndex(row, message); + int column = getColumn(index); + + //If the candidate index is in range and the column for the candidate has an error, proceed + if(0 <= index && index <= 195 && columns.get(column) && !path.contains(index)) + { + message.flip(index); + + if(isRowCorrect(row, message)) + { + path.add(index); + + //If the column is now correct, stop here and return the path as the solution + if(isColumnCorrect(column, message)) + { + columns.clear(column); + rows.clear(row); + return path; + } + + //Recursively test bit flipping in the column. A non-empty list is the solution set. + List solution = correctColumn(column, message, columns, rows, depth + 1, pursue, path); + + //If our candidate index is part of the discovered solution, fail, otherwise return it as part of + // the solution. + if(solution.size() > 0) + { + rows.clear(row); + return solution; + } + else if(pursue) //Pursue additional row flips + { + for(int row2 = rows.nextSetBit(row + 1); row2 >= 0 && row2 < 13; row2 = rows.nextSetBit(row2 + 1)) + { + List solution2 = correctRow(row2, message, columns, rows, depth + 1, pursue, path); + + if(solution2.size() > 0 && !solution2.contains(index)) + { + columns.clear(column); + rows.clear(row); + return solution2; + } + } + } + + path.removeLast(); + } + + //Revert the change + message.flip(index); + } + + return Collections.emptyList(); + } + + public static int getIndex(int column, int row) + { + return row * 15 + column + 1; + } + + /** + * Checks the row for bit errors. + * @param row to check + * @param message containing bits to check + * @return true if the row passes the Hamming check or false if not. + */ + private static boolean isRowCorrect(int row, CorrectedBinaryMessage message) + { + return Hamming15.getSyndrome(message, (row * 15) + 1) == 0; + } + + /** + * Calculates the index for the bit error in the message + * @param row to check + * @param message with bits + * @return error index in the range 0-195, or -1 if the message row has no bit errors. + */ + private static int getRowErrorIndex(int row, CorrectedBinaryMessage message) + { + return Hamming15.getErrorIndex(message, (row * 15) + 1); + } + + /** + * Checks the column for bit errors. + * @param column to check + * @param message containing bits to check + * @return true if the column passes the Hamming check or false if not. + */ + private static boolean isColumnCorrect(int column, CorrectedBinaryMessage message) + { + return Hamming13.getSyndrome(message, COLUMN_INDEXES[column]) == 0; + } + + /** + * Calculates the index for the bit error in the message. + * @param column to check + * @param message with bits. + * @return error index in the range 0-195, or -1 if message has no bit errors, or 1000 if message has more than 1 bit error. + */ + private static int getColumnErrorIndex(int column, CorrectedBinaryMessage message) + { + return Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]); + } + + public static int getColumn(int index) + { + return (index - 1) % 15; + } + + public static int getRow(int index) + { + return (index - 1) / 15; + } + + public static void main(String[] args) + { + String raw = "0000101111101000000100001010100001010000001000000011100000000000100000000000001000000000001000000000000010000000001111001010000100001000000001100000100001000111110101011111011111101001111011100010"; + String ref = "0000101111100000000100001100100001010000001000000000000000000000000000000000000000000000000000000000000000000000001111001010000100001000000001110000100001011111110101011011011111101011111011100000"; + CorrectedBinaryMessage rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw)); + CorrectedBinaryMessage refMessage = new CorrectedBinaryMessage(BinaryMessage.load(ref)); + +// correct2(refMessage); + + boolean pursueShadows = true; + correct(rawMessage, pursueShadows); + System.out.println("RAW: " + raw); + System.out.println("COR: " + rawMessage); + System.out.println("REF: " + ref); + + rawMessage.xor(refMessage); + System.out.println("RES: " + rawMessage); + + if(rawMessage.cardinality() > 0) + { + System.out.println("Residual Error Map:"); + logErrorMap(rawMessage); + } + + rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw)); + rawMessage.xor(refMessage); + System.out.println("\n\nXOR: " + rawMessage); + logErrorMap(rawMessage); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_68_36.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_68_36.java new file mode 100644 index 00000000..b04ac3cf --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_68_36.java @@ -0,0 +1,121 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.bptc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.Hamming17; + +/** + * Block product turbo code (BPTC) for decoding Short Link Control reassembled from CACH fragments. + */ +public class BPTC_68_36 extends BPTCBase +{ + public BPTC_68_36() + { + super(new Hamming17(), 17, 4); + } + + /** + * Performs error detection and correction and extracts the payload from the BPTC encoded message. + * @param message with BPTC encoding. + * @return decoded message. + */ + public CorrectedBinaryMessage extract(BinaryMessage message) + { + CorrectedBinaryMessage deinterleaved = deinterleave(message); + correct(deinterleaved); + + //Extract the message + CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(36); + + for(int row = 0; row < 3; row++) + { + for(int column = 0; column < 12; column++) + { + extractedMessage.set(row * 12 + column, deinterleaved.get(row * 17 + column)); + } + } + + extractedMessage.setCorrectedBitCount(deinterleaved.getCorrectedBitCount()); + return extractedMessage; + } + + + /** + * Deinterleaves the transmitted message. + * @param interleaved message + * @return deinterleaved message. + */ + public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved) + { + CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(68); + + for(int index = 0; index < 67; index++) + { + if(interleaved.get(index)) + { + deinterleaved.set((index * 17 % 67)); + } + } + + if(interleaved.get(67)) + { + deinterleaved.set(67); + } + + return deinterleaved; + } + + public static void main(String[] args) + { + BPTC_68_36 bptc = new BPTC_68_36(); + + String ref = "10101001100001000000001101101111000000110011001110011111101011001100"; //No errors + String raw = "10101001100001000000001101101111000000110011001110011111101011001100"; //Under test + CorrectedBinaryMessage rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw)); + CorrectedBinaryMessage refMessage = new CorrectedBinaryMessage(BinaryMessage.load(ref)); + + bptc.logDiagnostic(rawMessage); + + bptc.correct(rawMessage); + + System.out.println("RAW: " + raw); + System.out.println("COR: " + rawMessage); + System.out.println("REF: " + ref); + + rawMessage.xor(refMessage); + System.out.println("RES: " + rawMessage); + + if(rawMessage.cardinality() > 0) + { + System.out.println("--------------------------------"); + System.out.println("Residual Error Map:"); + bptc.logErrorMap(rawMessage); + System.out.println("--------------------------------"); + } + + System.out.println("\n\n*********** TRUTH DATA ********************"); + rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw)); + rawMessage.xor(refMessage); + System.out.println("XOR: " + rawMessage); + bptc.logErrorMap(rawMessage); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java index e8dbe5b2..613fe456 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java @@ -1,14 +1,49 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.message; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.module.decode.dmr.message.type.LCSS; +import java.util.HashMap; +import java.util.Map; /** * Common Announcement Channel is a 24-bit sequence that precedes an outbound DMR frame transmitted by a repeater. */ public class CACH { + //Generated by method: createHamming7_4BitErrorMap(). The value of the uncorrected 7-bit CACH is used as an index + //to lookup the bit error position where: + // 0-6: bit error index location where bits are ordered as transmitted: 0, 1 ..., 6 + // -1: valid/correct message + // -2: invalid message with 2 or more bit errors + public static final int[] BIT_ERROR_INDEXES = new int[]{-2, -2, -2, -2, -2, -2, -2, -2, -2, 5, 6, -1, 1, 2, 0, 4, + -2, 1, 4, 0, 5, 3, -1, 6, 0, 4, 1, 2, 6, -1, 3, 5, -2, -2, -2, -2, -2, -2, -2, -2, 4, 0, 2, 1, -1, 6, 5, -2, + 6, -1, 3, 5, 0, 4, 1, -2, 5, 3, -1, 6, 2, 1, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2, 2, 1, 4, 0, 5, -2, -1, 6, + 3, 5, 6, -1, 1, -2, 0, 4, -1, 6, 5, 3, 4, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2, 6, -1, -2, 5, 0, 4, 1, 2, + 4, 0, -2, 1, -1, 6, 5, 3, 1, 2, 0, 4, 3, 5, 6, -1}; + + private static final IntField VALUE_FIELD = IntField.range(0, 6); + private static final int CACH_MESSAGE_LENGTH = 24; private static final int[] INTERLEAVE_MATRIX = new int[]{0, 4, 8, 12, 14, 18, 22, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 15, 16, 17, 19, 20, 21, 23}; @@ -38,6 +73,23 @@ public class CACH mValid = valid; } + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + if(!isValid()) + { + sb.append("[CRC-ERROR] "); + } + sb.append(getInboundChannelAccessType().name()).append(" "); + sb.append(getLCSS()); + sb.append(" TS:").append(getTimeslot()); + sb.append(" CRC:").append(getCrcChecksum(getMessage())); + sb.append(" PAYLOAD:").append(getPayload().toHexString()); + sb.append(" MSG:").append(getMessage().toHexString()).append(" "); + return sb.toString(); + } + /** * Descrambled message bits for this cach */ @@ -64,18 +116,6 @@ public class CACH return false; } - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); -// sb.append(getMessage().toHexString()).append(" "); -// sb.append(getInboundChannelAccessType().name()).append(" "); - sb.append(getLCSS()); -// sb.append(" (").append(getTimeslot()).append("|").append(getCrcChecksum(getMessage())).append(")"); -// sb.append(" ").append(getPayload().toHexString()); - return sb.toString(); - } - /** * Indicates if this cach passes the FEC error correction and indicates that the inbound channel Access Type, * Timeslot and LCSS information is valid. @@ -109,17 +149,17 @@ public class CACH } /** - * Indicates the outbound timeslot for the next frame that follows this CACH, 0 or 1 + * Indicates the outbound timeslot for the next frame that follows this CACH, 1 or 2 */ public int getTimeslot() { - return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 1 : 0; + return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 2 : 1; } /** * Indicates if the timeslot is 0 */ - public boolean isTimeslot0() + public boolean isTimeslot1() { return !mMessage.get(OUTBOUND_BURST_TIMESLOT); } @@ -142,7 +182,6 @@ public class CACH { CorrectedBinaryMessage decodedMessage = new CorrectedBinaryMessage(CACH_MESSAGE_LENGTH); - //Deinterleave the transmitted message to create the decoded message for(int x = 0; x < CACH_MESSAGE_LENGTH; x++) { @@ -152,14 +191,15 @@ public class CACH } } - int checksum = getCrcChecksum(decodedMessage); + int codeword = decodedMessage.getInt(VALUE_FIELD); + int bitError = BIT_ERROR_INDEXES[codeword]; -// //debug - investigating corruption of CACH, SLC and EMB during encrypted Hytera/MotoTRBO calls. -// BinaryMessage test = message.getSubMessage(0, CACH_MESSAGE_LENGTH); -// System.out.println("--> CACH:" + test.toString() + " DE:" + decodedMessage.toString() + " CHK:" + checksum); - - boolean valid = (checksum == 0); + if(bitError >= 0) + { + decodedMessage.flip(bitError); + } + boolean valid = bitError != -2; return new CACH(decodedMessage, valid); } @@ -184,4 +224,101 @@ public class CACH return checksum; } + + public static void createHamming7_4BitErrorMap() + { + IntField valueField = IntField.range(0, 6); + Map map = new HashMap<>(); + + //Link Control Start/Stop value 00 is not valid for CACH, so we'll mark these as invalid codewords (-2). + int invalidLCSSMask = 0x18; //0011000b + + BinaryMessage parity0 = new BinaryMessage(7); + parity0.load(4, 3, 5); + BinaryMessage parity1 = new BinaryMessage(7); + parity1.load(4, 3, 7); + BinaryMessage parity2 = new BinaryMessage(7); + parity2.load(4, 3, 6); + BinaryMessage parity3 = new BinaryMessage(7); + parity3.load(4, 3, 3); + + //There are 2^4 = 16 potential valid codewords + for(int x = 0; x < 16; x++) + { + BinaryMessage bm = new BinaryMessage(7); + bm.load(0, 4, x); + + if(bm.get(0)) + { + bm.xor(parity0); + } + if(bm.get(1)) + { + bm.xor(parity1); + } + if(bm.get(2)) + { + bm.xor(parity2); + } + if(bm.get(3)) + { + bm.xor(parity3); + } + + int value = bm.getInt(valueField); + + //Ignore any legal codeword that has a value of 00 in the LCSS field. + if(((value & invalidLCSSMask) ^ invalidLCSSMask) == invalidLCSSMask) + { + continue; + } + + //Use -1 to indicate no errors and we'll reserve -2 for invalid/non-correctable 2-bit errors. + map.put(value, -1); + + //Iteratively induce single bit errors and record them into the map + for(int bitError = 0; bitError < 7; bitError++) + { + bm.flip(bitError); + value = bm.getInt(valueField); + + //Ignore any bit combination with a 00 in the LCSS field. + if(((value & invalidLCSSMask) ^ invalidLCSSMask) != invalidLCSSMask) + { + map.put(value, bitError); + } + + bm.flip(bitError); + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("public static final int[] BIT_ERROR_INDEXES = new int[]{"); + + for(int z = 0; z < 128; z++) + { + if(map.containsKey(z)) + { + sb.append(map.get(z)); + } + else + { + sb.append("-2"); + } + + if(z != 127) + { + sb.append(","); + } + } + + sb.append("};"); + + System.out.println(sb); + } + + public static void main(String[] args) + { + createHamming7_4BitErrorMap(); + } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java index 06db716d..d496419c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java @@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Base DMR Burst Message class */ diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java index a9d19cba..2dc30dc6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java @@ -20,63 +20,21 @@ package io.github.dsheirer.module.decode.dmr.message; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.TimeslotMessage; import io.github.dsheirer.protocol.Protocol; /** * Base DMR Message */ -public abstract class DMRMessage extends Message +public abstract class DMRMessage extends TimeslotMessage { - public static final int TIMESLOT_1 = 1; - public static final int TIMESLOT_2 = 2; - - private CorrectedBinaryMessage mCorrectedBinaryMessage; - private boolean mValid = true; - private int mTimeslot; - /** * Constructs an instance * @param timestamp for the message */ public DMRMessage(CorrectedBinaryMessage message, long timestamp, int timeslot) { - super(timestamp); - mCorrectedBinaryMessage = message; - mTimeslot = timeslot; - } - - /** - * Message bits - */ - public CorrectedBinaryMessage getMessage() - { - return mCorrectedBinaryMessage; - } - - /** - * Indicates if this message is valid - */ - public boolean isValid() - { - return mValid; - } - - /** - * Sets the valid flag for this message - */ - public void setValid(boolean valid) - { - mValid = valid; - } - - /** - * Timeslot for this message - * @return 0 or 1 - */ - public int getTimeslot() - { - return mTimeslot; + super(message, timeslot, timestamp); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java index 96d04b0e..cc898e11 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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,11 +20,14 @@ package io.github.dsheirer.module.decode.dmr.message; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.message.EmptyTimeslotPlaceholderMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.dmr.message.data.DMRDataMessageFactory; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceAMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,39 +43,33 @@ public class DMRMessageFactory * @param timeslot for the burst * @return DMRMessage instance or null for UNKNOWN sync pattern */ - public static DMRBurst create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach, + public static IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach, long timestamp, int timeslot) { switch(syncPattern) { - case BASE_STATION_VOICE: - case MOBILE_STATION_VOICE: - case BS_VOICE_FRAME_B: - case BS_VOICE_FRAME_C: - case BS_VOICE_FRAME_D: - case BS_VOICE_FRAME_E: - case BS_VOICE_FRAME_F: - - case MS_VOICE_FRAME_B: - case MS_VOICE_FRAME_C: - case MS_VOICE_FRAME_D: - case MS_VOICE_FRAME_E: - case MS_VOICE_FRAME_F: + case BASE_STATION_VOICE, MOBILE_STATION_VOICE: + case BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F: + case MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F: + case DIRECT_VOICE_FRAME_B, DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E, + DIRECT_VOICE_FRAME_F: return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, timeslot); - case DIRECT_MODE_VOICE_TIMESLOT_1: + case DIRECT_VOICE_TIMESLOT_1: return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 1); - case DIRECT_MODE_VOICE_TIMESLOT_2: + case DIRECT_VOICE_TIMESLOT_2: return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 2); case BASE_STATION_DATA: case MOBILE_STATION_DATA: return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot); - case DIRECT_MODE_DATA_TIMESLOT_1: + case DIRECT_DATA_TIMESLOT_1: return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1); - case DIRECT_MODE_DATA_TIMESLOT_2: + case DIRECT_DATA_TIMESLOT_2: return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2); + case DIRECT_EMPTY_TIMESLOT: + return new EmptyTimeslotPlaceholderMessage(timestamp, 288, Protocol.DMR, timeslot); case UNKNOWN: return null; - case MOBILE_STATION_REVERSE_CHANNEL: + case REVERSE_CHANNEL: case RESERVED: default: return new UnknownDMRMessage(syncPattern, binaryMessage, cach, timestamp, timeslot); @@ -95,14 +92,13 @@ public class DMRMessageFactory { case BASE_STATION_VOICE: case MOBILE_STATION_VOICE: - case DIRECT_MODE_VOICE_TIMESLOT_1: - case DIRECT_MODE_VOICE_TIMESLOT_2: + case DIRECT_VOICE_TIMESLOT_1: + case DIRECT_VOICE_TIMESLOT_2: return new VoiceAMessage(syncPattern, message, cach, timestamp, timeslot); - case BS_VOICE_FRAME_B: - case BS_VOICE_FRAME_C: - case BS_VOICE_FRAME_D: - case BS_VOICE_FRAME_E: - case BS_VOICE_FRAME_F: + case BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F: + case MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F: + case DIRECT_VOICE_FRAME_B, DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E, + DIRECT_VOICE_FRAME_F: default: return new VoiceEMBMessage(syncPattern, message, cach, timestamp, timeslot); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java index 81afa0ea..4b13ffbb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java @@ -21,8 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java index f21c6a04..e3da332a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data; import io.github.dsheirer.bits.BitSetFullException; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.edac.BPTC_196_96; import io.github.dsheirer.edac.CRCDMR; import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_DMR; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.bptc.BPTC_196_96; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1Rate; import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1_2Rate; @@ -52,6 +51,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData; import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,33 +79,73 @@ public class DMRDataMessageFactory if(slotType.isValid()) { + switch (slotType.getDataType()) + { + case RATE_3_OF_4_DATA: + return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot); + case RATE_1_DATA: + return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot); + } + + //Get the BPTC(196,96) protected payload. If the corrected bit count is -1, the payload/message is invalid + CorrectedBinaryMessage payload = getPayload(message); + switch(slotType.getDataType()) { case SLOT_IDLE: - return new IDLEMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage idle = new IDLEMessage(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + idle.setValid(false); + } + return idle; case CSBK: - return CSBKMessageFactory.create(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage csbk = CSBKMessageFactory.create(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + csbk.setValid(false); + } + return csbk; case USB_DATA: - CorrectedBinaryMessage usbdPayload = getPayload(message); + CorrectedBinaryMessage usbdPayload = payload; switch(USBData.getServiceType(usbdPayload)) { case LIP_SHORT_LOCATION_REQUEST: default: - return new USBData(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage usb =new USBData(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + usb.setValid(false); + } + return usb; } case MBC_ENC_HEADER: case MBC_HEADER: - return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage mbc = new MBCHeader(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + mbc.setValid(false); + } + return mbc; case CHANNEL_CONTROL_ENC_HEADER: case PI_HEADER: - return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + pi.setValid(false); + } + return pi; case VOICE_HEADER: - return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + voice.setValid(false); + } + return voice; case DATA_ENC_HEADER: case DATA_HEADER: - CorrectedBinaryMessage payload = getPayload(message); int crcBitCount = CRCDMR.correctCCITT80(payload, 0, 80, 0xCCCC); - boolean valid = crcBitCount < 2; + boolean valid = crcBitCount < 2 && payload.getCorrectedBitCount() != -1; DataPacketFormat dpf = DataHeader.getDataPacketFormat(payload); switch(dpf) @@ -191,22 +231,44 @@ public class DMRDataMessageFactory return dh; } case TLC: - return new Terminator(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + tlc.setValid(false); + } + return tlc; case RATE_1_OF_2_DATA: - return new DataBlock1_2Rate(pattern, getPayload(message), cach, slotType, timestamp, timeslot); - case RATE_3_OF_4_DATA: - return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot); - case RATE_1_DATA: - return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot); + DataMessage data = new DataBlock1_2Rate(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + data.setValid(false); + } + return data; case MBC_BLOCK: - return new MBCContinuationBlock(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage mbcBlock = new MBCContinuationBlock(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + mbcBlock.setValid(false); + } + return mbcBlock; case RESERVED_15: case UNKNOWN: - return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + DataMessage unk = new UnknownDataMessage(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + unk.setValid(false); + } + return unk; } } - return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + CorrectedBinaryMessage payload = getPayload(message); + DataMessage unk = new UnknownDataMessage(pattern, payload, cach, slotType, timestamp, timeslot); + if(payload.getCorrectedBitCount() < 0) + { + unk.setValid(false); + } + return unk; } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java index 7d3127b5..48d1b806 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java @@ -19,9 +19,9 @@ package io.github.dsheirer.module.decode.dmr.message.data; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.DMRBurst; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Base DMR Data Message diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java index 5a20131b..702f437f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java @@ -21,11 +21,11 @@ package io.github.dsheirer.module.decode.dmr.message.data; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory; import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java index 60a6924b..cfc667c6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This 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.module.decode.dmr.message.data; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; @@ -54,6 +54,11 @@ public class IDLEMessage extends DataMessage sb.append(" RAS:").append(getBPTCReservedBits()); } + if(!isValid()) + { + sb.append(" [CRC-ERROR]"); + } + sb.append(" IDLE"); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java index ad5e9b35..39fe902b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java @@ -21,8 +21,8 @@ package io.github.dsheirer.module.decode.dmr.message.data; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java index 4026b5e6..912c0f79 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.block; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.DataMessage; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java index e7194f38..a6fb56a5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java @@ -20,9 +20,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.block; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Rate 1/1 Data Block diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java index a575083a..b09bd837 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java @@ -20,9 +20,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.block; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Rate 1/2 BPTC 196 Data Block diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java index 145cd497..59fbed93 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java @@ -20,9 +20,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.block; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Rate 3/4 Trellis Coded Data Block diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java index 6c956fe6..55711ab0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java @@ -20,9 +20,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.block; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Unknown DMR data block diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java index 7329e39d..7430d133 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java @@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRCDMR; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.DataMessage; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Control Signalling Block (CSBK) Message diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java index 9575de80..65e40796 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java @@ -20,7 +20,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera08Acknowledge; @@ -88,6 +87,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK; import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.List; /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java index 99c5610f..37af0675 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java index 17ad6c59..d3f3c222 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; @@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType; import io.github.dsheirer.module.decode.dmr.message.type.Reason; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java index c8a4b625..ae440275 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; @@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType; import io.github.dsheirer.module.decode.dmr.message.type.Reason; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java index 10da342a..8853c6d3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java @@ -20,11 +20,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AdjacentSiteInformation; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Hytera Neighbor/Adjacent Site Information diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java index 6412c40a..a6fcabf7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; import io.github.dsheirer.module.decode.dmr.message.type.Version; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java index a5c0edbf..8ac3e359 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java @@ -22,12 +22,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java index d6879f7a..ef341593 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java index ca3b5205..3379cb70 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java index fc5726c1..4eec6c34 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java index 4db18381..58c66017 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java index 81753420..c32da89f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java index dc96bf4c..33e36dcc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java index f0b10fc9..f2adb5cc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java index 32fc3fd2..33e58538 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; import io.github.dsheirer.module.decode.dmr.message.type.Version; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java index 0e1df375..745d5d38 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; @@ -30,7 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java index 446cd86b..d965e4a4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java index b663f5c6..1a3ef86a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java index 9518a49e..6e4a2beb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java index c228ae9b..08fa4e2e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java @@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.LCSS; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java index 0483d579..5770dbf0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java index bef140fd..48318c01 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java @@ -24,7 +24,6 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; @@ -34,6 +33,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java index ded57642..daa98445 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java index dedd64f9..ba9d82d8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java index 6213c4ae..5485cd94 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java index 73bc3c4a..d9b7d9aa 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java index 76965fe7..a811dac8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java @@ -23,11 +23,11 @@ import com.google.common.base.Joiner; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.site.SiteIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRSite; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java index 1da6ac4a..0fe6d6db 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java @@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPMessageType; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java index d37c5fe6..68f0843e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java index 552b1a12..0c2cc603 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java index 1acd93ed..70187273 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java index 4719fcb2..56d3a8ae 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java index e09ca5d3..e8e4113c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; @@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java index 63c320ba..16b84246 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; import io.github.dsheirer.module.decode.dmr.message.type.Version; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java index 640c11f0..c346dbbe 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; @@ -36,6 +35,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java index d3579412..49dfe3e6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java @@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; @@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java index 387e6bc2..92c2d8ed 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java index aa476e29..855d729f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java @@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.ProtectKind; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java index 2cc099d5..6a439cac 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; @@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType; import io.github.dsheirer.module.decode.dmr.message.type.Reason; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java index 8ac0c323..25fb5fb2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java @@ -2,11 +2,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowle import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java index 6ebe196a..0413a1c4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java @@ -1,9 +1,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Registration Accepted diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java index 2ae9e021..2d1e6327 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java @@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.integer.IntegerIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * DMR Tier III - Ahoy! diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java index 3cdcc7e9..4299af8d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java index 25226f5c..a8b45f2a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java @@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java index ba886f7a..5b555a8c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java @@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java index 29757be8..47d85b51 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java index af9d57c2..ee52fae1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java index bb3a0c35..48cc5fc9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java @@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.message.CACH; @@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java index 3213f402..dfcf7c8d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java @@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java index 99a09bb3..c37b3a46 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java @@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.message.CACH; @@ -29,6 +28,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java index 4e6d2ab6..9afdb3d2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java @@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java index 979228b1..7e2f2521 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java @@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java index 4f6d95ae..43d9dbff 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java @@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java index 5b751b34..ac0d19b7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java @@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java index 5850895b..e22ac7c3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java @@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java index dff50189..f3b49efd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java index 55578103..02fbddc8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java @@ -20,7 +20,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; @@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.List; /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java index 6e42736a..a90c3bd6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java index c9fdcca7..641f325d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java index f643f66e..168dcce5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java index d6888909..5eec1333 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java @@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java index e44f46a8..836ae8fd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java index a8220da1..631dd169 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java index 83714ce5..fda3ce9b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java @@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java index fbac812f..e029b28e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java @@ -20,10 +20,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Data Header message. diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java index 893b5fd5..2bba75a2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java @@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.DefinedDataFormat; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java index 8558f340..ce38dd41 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java @@ -20,10 +20,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Header data message. diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java index 0923ce63..11d9ab62 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java @@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode; import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java index 04c4c907..ca7b0c08 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java @@ -20,9 +20,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Packet sequence header that contains octet (byte) data diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java index 70b932cf..bc3e2181 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java @@ -22,12 +22,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Packet Sequence Data Header diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java index 0a46442f..764d42d6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java @@ -20,11 +20,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * PI/Encryption Header with Link Control diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java index 33b8eef1..14561199 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java @@ -21,11 +21,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Proprietary Data Header diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java index 0cc0ee1a..390553cd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java @@ -20,9 +20,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Raw Short Data Header diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java index f919f789..7c397905 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java @@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.ResponseStatus; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java index 7fec9358..323f11c5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java index 314519f4..d345c81d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java @@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus; import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java index ee5f96c4..d126ef5e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.CACH; @@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; import io.github.dsheirer.module.decode.dmr.message.type.UnifiedDataTransportFormat; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java index 11c89810..c2f5aa6e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java @@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; - +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java index b4435000..810ebbf9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java @@ -20,7 +20,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java index 2f579d78..549916a3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java @@ -20,11 +20,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.header.hytera; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Hytera Proprietary Data Header for SAP Short Data diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java index b3346d51..eafc19c0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java @@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.header.motorola; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Motorola MOTOTRBO Network Interface Service (MNIS) Data Header for SAP:1 diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java index c81de344..6c72bf47 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java @@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.header.motorola; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint; import io.github.dsheirer.module.decode.dmr.message.type.Vendor; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Motorola Proprietary Data Header diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java index a65dd2c6..2e9c03d1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java @@ -116,7 +116,7 @@ public class LCMessageFactory //Some Hytera Tier-3 systems use a zero-valued mask when the FLC is carried in the voice header or terminator //and in the same transmission it uses the standard mask when carried across the voice frames. It doesn't make //sense, but maybe it's a bug in their software implementation? - if(!valid && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER) + if(!valid && message.size() == 96 && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER) { //Retry the RS(12,9,4) with a mask value of zero valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message, 0); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java index 2e1439ae..2705248d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.BitSetFullException; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.edac.Hamming16; +import io.github.dsheirer.module.decode.dmr.bptc.BPTC_128_77; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory; import io.github.dsheirer.module.decode.dmr.message.type.LCSS; import org.slf4j.Logger; @@ -38,6 +38,7 @@ public class FLCAssembler private int mFragmentCount = 0; private long mTimestamp; private int mTimeslot; + private BPTC_128_77 mBPTC = new BPTC_128_77(); /** * Constructs an instance @@ -47,6 +48,15 @@ public class FLCAssembler mTimeslot = timeslot; } + /** + * Resets this assembler when a sync loss is detected. + */ + public void reset() + { + mAssemblingMessage = null; + mFragmentCount = 0; + } + /** * Processes the full link control fragment * @return a full link control message or null if the message is not yet fully assembled @@ -101,7 +111,8 @@ public class FLCAssembler if(mFragmentCount == 4) { - message = decode(mAssemblingMessage, mTimestamp, mTimeslot); +// message = decode(mAssemblingMessage, mTimestamp, mTimeslot); + message = decode2(mAssemblingMessage, mTimestamp, mTimeslot); } mAssemblingMessage = null; @@ -112,88 +123,124 @@ public class FLCAssembler /** * Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message */ - private static FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot) + private FullLCMessage decode2(BinaryMessage message, long timestamp, int timeslot) { if(message != null) { - boolean valid = true; - - CorrectedBinaryMessage descrambled = new CorrectedBinaryMessage(128); - int src; - descrambled.set(127, message.get(127)); - for(int i = 0; i < 127; i++) - { - src = (i * 8) % 127; - boolean x = message.get(src); - descrambled.set(i, message.get(src)); - } - - int errorCount = 0; - - for(int row = 0; row < 8; row++) - { - int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16); - - errorCount += rowErrorCount; - - if(errorCount > 1) - { - valid = false; - } - } - - //If valid, check the column parity values - if(valid) - { - //Check the column parity values - for(int column = 0; column < 16; column++) - { - boolean parity = descrambled.get(column); - - for(int y = 1; y < 8; y++) - { - parity ^= descrambled.get(column + (y * 16)); - } - - if(parity) - { - valid = false; - } - } - } - - //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits. - CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77); - int pointer = 0; - - for(int row = 0; row < 2; row++) - { - for(int column = 0; column < 11; column++) - { - extractedMessage.set(pointer++, descrambled.get(row * 16 + column)); - } - } - - for(int row = 2; row < 7; row++) - { - for(int column = 0; column < 10; column++) - { - extractedMessage.set(pointer++, descrambled.get(row * 16 + column)); - } - - //Transfer the checksum parity bit from this row - extractedMessage.set(70 + row, descrambled.get(row * 16 + 10)); - } - - extractedMessage.setCorrectedBitCount(errorCount); + CorrectedBinaryMessage extractedMessage = mBPTC.extract(message); FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false); - flco.setValid(valid); + flco.setValid(extractedMessage.getCorrectedBitCount() >= 0); return flco; } return null; } + +// /** +// * Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message +// */ +// private static FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot) +// { +// if(message != null) +// { +// System.out.println("FLC Raw:" + message); +// boolean valid = true; +// +// CorrectedBinaryMessage descrambled = new CorrectedBinaryMessage(128); +// int src; +// descrambled.set(127, message.get(127)); +// for(int i = 0; i < 127; i++) +// { +// src = (i * 8) % 127; +// boolean x = message.get(src); +// descrambled.set(i, message.get(src)); +// } +// +// System.out.println("FLC Des:" + descrambled); +// int errorCount = 0; +// +// for(int row = 0; row < 8; row++) +// { +// int errorIndex = Hamming16.getErrorIndex(descrambled, row * 16); +// BinaryMessage rowErrors = BPTC_128_77.getRowErrors(descrambled); +// BinaryMessage columnErrors = BPTC_128_77.getColumnErrors(descrambled); +// +// int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16); +// +// if(rowErrorCount == 0) +// { +// System.out.println("Row [" + row + "]"); +// } +// else +// { +// System.out.println("Row [" + row + "] Error Count: " + rowErrorCount + " Index:" + errorIndex + +// " Value: " + message.get(errorIndex) + +// " Row Errors: " + rowErrors + " Column Errors: " + columnErrors ); +// } +// +// errorCount += rowErrorCount; +// +// if(errorCount > 1) +// { +// valid = false; +// } +// } +// +// //If valid, check the column parity values +// if(valid) +// { +// //Check the column parity values +// for(int column = 0; column < 16; column++) +// { +// boolean parity = descrambled.get(column); +// +// for(int y = 1; y < 8; y++) +// { +// parity ^= descrambled.get(column + (y * 16)); +// } +// +// if(parity) +// { +// valid = false; +// } +// } +// } +// +// //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits. +// CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77); +// int pointer = 0; +// +// for(int row = 0; row < 2; row++) +// { +// for(int column = 0; column < 11; column++) +// { +// extractedMessage.set(pointer++, descrambled.get(row * 16 + column)); +// } +// } +// +// for(int row = 2; row < 7; row++) +// { +// for(int column = 0; column < 10; column++) +// { +// extractedMessage.set(pointer++, descrambled.get(row * 16 + column)); +// } +// +// //Transfer the checksum parity bit from this row +// extractedMessage.set(70 + row, descrambled.get(row * 16 + 10)); +// } +// +// extractedMessage.setCorrectedBitCount(errorCount); +// +// +// FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false); +// flco.setValid(valid); +// return flco; +// } +// +// return null; +// } + /** * Adds the cach payload to the currently assembling message */ diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java index 80d3fba9..fb4e15e9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -68,7 +68,6 @@ public class GroupVoiceChannelUser extends AbstractVoiceChannelUser sb.append(getRadio()); sb.append(" TO:").append(getTalkgroup()); sb.append(" ").append(getServiceOptions()); - sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java index 32eda818..ec477fdc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java @@ -100,6 +100,12 @@ public class TalkerAliasHeader extends FullLCMessage public String toString() { StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("[CRC-ERROR] "); + } + sb.append("FLC TALKER ALIAS HEADER"); sb.append(" FORMAT:").append(getFormat()); sb.append(" CHARACTERS:").append(getCharacterLength()); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java index 3794f389..2c2f404d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.identifier.DMRNetwork; import io.github.dsheirer.module.decode.dmr.identifier.DMRSite; - import java.util.ArrayList; import java.util.List; @@ -59,6 +58,7 @@ public class ConnectPlusControlChannel extends ShortLCMessage } sb.append("SLC MOTOROLA CON+ CONTROL CHANNEL NETWORK:").append(getNetwork()); sb.append(" SITE:").append(getSite()); + sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java index 0bb70579..1cd84a42 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.BitSetFullException; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.edac.Hamming17; +import io.github.dsheirer.module.decode.dmr.bptc.BPTC_68_36; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory; import io.github.dsheirer.module.decode.dmr.message.type.LCSS; import org.slf4j.Logger; @@ -37,6 +37,7 @@ public class SLCAssembler private CorrectedBinaryMessage mAssemblingMessage; private int mFragmentCount = 0; private long mTimestamp; + private BPTC_68_36 mBPTC = new BPTC_68_36(); /** * Constructs an instance @@ -45,6 +46,16 @@ public class SLCAssembler { } + /** + * Resets this assembler when a sync loss is detected to prevent malformed SLC messages. + */ + public void reset() + { + mAssemblingMessage = null; + mFragmentCount = 0; + mTimestamp = System.currentTimeMillis(); + } + /** * Processes the short link control fragment * @return a short link control message or null if the message is not yet fully assembled @@ -121,80 +132,20 @@ public class SLCAssembler return message; } - private static ShortLCMessage decode(BinaryMessage interleaved, long timestamp) + /** + * Performs error detection and correction and extracts the message from the BPTC protected interleaved message. + * @param interleaved message + * @param timestamp for the decoded message. + * @return decoded message. + */ + private ShortLCMessage decode(BinaryMessage interleaved, long timestamp) { if(interleaved != null) { - boolean valid = true; - - CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(68); - int i, src; - - for(int index = 0; index < 67; index++) - { - if(interleaved.get(index)) - { - deinterleaved.set((index * 17 % 67)); - } - } - - if(interleaved.get(67)) - { - deinterleaved.set(67); - } - - int errorCount = 0; - - for(int row = 0; row < 4; row++) - { - int rowErrorCount = Hamming17.checkAndCorrect(deinterleaved, row * 17); - errorCount += rowErrorCount; - - if(rowErrorCount > 1) - { - valid = false; - } - } - - //If valid, check the column parity values - if(valid) - { - //Check the column parity values - for(int column = 0; column < 17; column++) - { - boolean parity = deinterleaved.get(column); - - for(int y = 1; y < 4; y++) - { - parity ^= deinterleaved.get(column + (y * 17)); - } - - if(parity) - { - valid = false; - } - } - } - - //Extract the message - CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(51); - - for(int row = 0; row < 3; row++) - { - for(int column = 0; column < 12; column++) - { - extractedMessage.set(row * 12 + column, deinterleaved.get(row * 17 + column)); - } - } - - extractedMessage.setCorrectedBitCount(errorCount); - extractedMessage.setSize(36); - ShortLCMessage slco = LCMessageFactory.createShort(extractedMessage, timestamp, 0); - - + CorrectedBinaryMessage corrected = mBPTC.extract(interleaved); //Note: slco is timeslot-agnostic, so we use timeslot 0 every time -// ShortLCMessage slco = LCMessageFactory.createShort(decoded, timestamp, 0); - slco.setValid(valid); + ShortLCMessage slco = LCMessageFactory.createShort(corrected, timestamp, 0); + slco.setValid(corrected.getCorrectedBitCount() >= 0); return slco; } @@ -231,18 +182,4 @@ public class SLCAssembler dispatch(); } } - - public static void main(String[] args) - { - String raw = "A0000C0000A0000C0000"; - System.out.println(" RAW:" + raw); - String bits = "10100000000000000110000000000000001010000000000000011000000000000000"; - - BinaryMessage rawBin= BinaryMessage.load(bits); - System.out.println("ORIG:" + rawBin.toHexString()); - - ShortLCMessage slc = decode(rawBin, System.currentTimeMillis()); - - System.out.println(slc.toString()); - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java index 4b865312..689991d1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java @@ -20,10 +20,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.mbc; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.DataMessage; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java index d8013377..a8aa0333 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java @@ -20,10 +20,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.terminator; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; /** * Terminator data message with link control diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java index 199b40ed..94b8fae5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java @@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRCDMR; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.DataMessage; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.type.ServiceType; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java index 8eab4622..b3cb07b6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; import java.util.function.Function; @@ -51,8 +51,8 @@ public class VoiceMessageFilter extends Filter add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_D)); add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_E)); add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_F)); - add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_1)); - add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_2)); + add(new FilterElement<>(DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1)); + add(new FilterElement<>(DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2)); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java index 9300db1f..c96f5868 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java @@ -21,8 +21,8 @@ package io.github.dsheirer.module.decode.dmr.message.voice; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import java.util.Collections; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java index fb026978..4214ab14 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java @@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.voice; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java index a092ab65..3054c213 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java @@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.voice; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.DMRBurst; import java.util.ArrayList; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java index c4bd455d..38d8b087 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.voice; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.edac.BPTC_16_2; import io.github.dsheirer.edac.Golay24; +import io.github.dsheirer.module.decode.dmr.bptc.BPTC_16_2; import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; +import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters; -import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NonStandardShortBurst; import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst; import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst; @@ -183,7 +183,7 @@ public class VoiceSuperFrameProcessor return switch(opcode) { case NULL -> new NullShortBurst(decoded); - case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EncryptionParameters(decoded); + case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EmbeddedEncryptionParameters(decoded); case TXI_DELAY -> new TransmitInterrupt(decoded); default -> new UnknownShortBurst(decoded); }; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedEncryptionParameters.java similarity index 93% rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedEncryptionParameters.java index 987bc00c..12b2be93 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedEncryptionParameters.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm; /** * Encryption parameters short burst payload. */ -public class EncryptionParameters extends ShortBurst +public class EmbeddedEncryptionParameters extends ShortBurst { private static final int[] KEY = new int[]{0, 1, 2, 3, 4, 5, 6, 7}; private static final int[] ALGORITHM = new int[]{8, 9, 10}; @@ -35,7 +35,7 @@ public class EncryptionParameters extends ShortBurst * * @param message containing the de-interleaved and error-corrected short burst payload. */ - public EncryptionParameters(CorrectedBinaryMessage message) + public EmbeddedEncryptionParameters(CorrectedBinaryMessage message) { super(message); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java new file mode 100644 index 00000000..3293e5f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java @@ -0,0 +1,113 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import io.github.dsheirer.dsp.symbol.Dibit; + +/** + * DMR sync detector that processes hard symbol (ie dibit) decisions. + */ +public class DMRHardSyncDetector extends DMRSyncDetector +{ + private static final long SYNC_MASK = 0xFFFFFFFFFFFFL; + private static final int MAXIMUM_BIT_ERROR = 5; + private long mValue; + private DMRSyncPattern mDetectedPattern = DMRSyncPattern.UNKNOWN; + + /** + * Constructs an instance + */ + public DMRHardSyncDetector() + { + } + + public DMRSyncPattern getDetectedPattern() + { + return mDetectedPattern; + } + + /** + * Processes the dibit. + * @param dibit to process + * @return true if a sync pattern is detected. + */ + public boolean process(Dibit dibit) + { + mValue = (Long.rotateLeft(mValue, 2) & SYNC_MASK) + dibit.getValue(); + + int bitCount = Long.bitCount(mValue ^ DMRSyncPattern.BASE_STATION_DATA.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.BASE_STATION_VOICE.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.MOBILE_STATION_DATA.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.MOBILE_STATION_VOICE.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_DATA_TIMESLOT_1.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_DATA_TIMESLOT_2.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + return true; + } + + bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2.getPattern()); + if(bitCount <= MAXIMUM_BIT_ERROR) + { + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + return true; + } + + return false; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java new file mode 100644 index 00000000..00120cea --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java @@ -0,0 +1,93 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import io.github.dsheirer.dsp.symbol.Dibit; +import java.util.Arrays; + +/** + * DMR Sync Detector. Uses soft demodulated symbols to detect sync patterns in the demodulated symbol stream. + */ +public abstract class DMRSoftSyncDetector extends DMRSyncDetector +{ + protected static final float MAX_POSITIVE = Dibit.D01_PLUS_3.getIdealPhase(); + protected static final float MAX_NEGATIVE = Dibit.D11_MINUS_3.getIdealPhase(); + protected static final float[] BASE_DATA = DMRSyncPattern.BASE_STATION_DATA.toSymbols(); + protected static final float[] BASE_VOICE = DMRSyncPattern.BASE_STATION_VOICE.toSymbols(); + protected static final float[] MOBILE_DATA = DMRSyncPattern.MOBILE_STATION_DATA.toSymbols(); + protected static final float[] MOBILE_VOICE = DMRSyncPattern.MOBILE_STATION_VOICE.toSymbols(); + protected static final float[] DIRECT_DATA_1 = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1.toSymbols(); + protected static final float[] DIRECT_DATA_2 = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2.toSymbols(); + protected static final float[] DIRECT_VOICE_1 = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1.toSymbols(); + protected static final float[] DIRECT_VOICE_2 = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2.toSymbols(); + + protected float[] mSymbols = new float[48]; //2x longer than 48-bit/24-dibit DMR sync patterns. + protected int mSymbolPointer = 0; + + public DMRSoftSyncDetector() + { + } + + /** + * Resets this sync detector and flushes any stored soft symbols from the buffer. + */ + public void reset() + { + Arrays.fill(mSymbols, 0.0f); + mSymbolPointer = 0; + } + + /** + * Detected DMR sync pattern that produced the highest correlation score from the most recent call to process(). + * @return detected sync pattern. + */ + public DMRSyncPattern getDetectedPattern() + { + return mDetectedPattern; + } + + /** + * Worker method to be implemented by the subclass implementation. + * @return best sync correlation score where the best detected pattern is available at + * @see DmrSyncDetector#getDetectedPattern() method. + */ + protected abstract float calculate(); + + /** + * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft + * symbols that include this recent soft symbol. + * + * @param dibitSymbol to process that represents the soft demodulated symbol value in phase angle/radians. + * @return correlation score. + */ + public float process(float dibitSymbol) + { + //Constrain symbol to maximum positive or negative value to limit noisy symbols. + dibitSymbol = Math.min(dibitSymbol, MAX_POSITIVE); + dibitSymbol = Math.max(dibitSymbol, MAX_NEGATIVE); + + mSymbols[mSymbolPointer] = dibitSymbol; + mSymbols[mSymbolPointer + 24] = dibitSymbol; + mSymbolPointer++; + mSymbolPointer %= 24; + + return calculate(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorFactory.java new file mode 100644 index 00000000..8e29d8a0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorFactory.java @@ -0,0 +1,59 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +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 DMR Soft Sync Detectors + */ +public class DMRSoftSyncDetectorFactory +{ + private static final Logger mLog = LoggerFactory.getLogger(DMRSoftSyncDetectorFactory.class); + + /** + * Creates the implementation using calibration data to select the optimal + * implementation from scalar and vector options. + * @return demodulator instance + */ + public static DMRSoftSyncDetector getDetector() + { + Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DMR_SOFT_SYNC_DETECTOR); + + switch(implementation) + { + case VECTOR_SIMD_64: + return new DMRSoftSyncDetectorVector64(); + case VECTOR_SIMD_128: + return new DMRSoftSyncDetectorVector128(); + case VECTOR_SIMD_256: + return new DMRSoftSyncDetectorVector256(); + case VECTOR_SIMD_512: + return new DMRSoftSyncDetectorVector512(); + case SCALAR: + default: + return new DMRSoftSyncDetectorScalar(); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorScalar.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorScalar.java new file mode 100644 index 00000000..49353f11 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorScalar.java @@ -0,0 +1,192 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +/** + * Scalar implementation of DMR Soft Sync Detector. + */ +public class DMRSoftSyncDetectorScalar extends DMRSoftSyncDetector +{ + /** + * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft + * symbols that include this recent soft symbol. + * + * @return correlation score. + */ + public float calculate() + { + float symbol; + + switch(mMode) + { + case AUTOMATIC: + float allBaseData = 0; + float allBaseVoice = 0; + float allMobileData = 0; + float allMobileVoice = 0; + float allDirectData1 = 0; + float allDirectData2 = 0; + float allDirectVoice1 = 0; + float allDirectVoice2 = 0; + + for(int x = 0; x < 24; x++) + { + symbol = mSymbols[mSymbolPointer + x]; + + allBaseData += BASE_DATA[x] * symbol; + allBaseVoice += BASE_VOICE[x] * symbol; + allMobileData += MOBILE_DATA[x] * symbol; + allMobileVoice += MOBILE_VOICE[x] * symbol; + allDirectData1 += DIRECT_DATA_1[x] * symbol; + allDirectData2 += DIRECT_DATA_2[x] * symbol; + allDirectVoice1 += DIRECT_VOICE_1[x] * symbol; + allDirectVoice2 += DIRECT_VOICE_2[x] * symbol; + } + + float allBestScore = allBaseData; + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + if(allBaseVoice > allBestScore) + { + allBestScore = allBaseVoice; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + if(allMobileData > allBestScore) + { + allBestScore = allMobileData; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + } + + if(allMobileVoice > allBestScore) + { + allBestScore = allMobileVoice; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + if(allDirectData1 > allBestScore) + { + allBestScore = allDirectData1; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + } + + if(allDirectData2 > allBestScore) + { + allBestScore = allDirectData2; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + if(allDirectVoice1 > allBestScore) + { + allBestScore = allDirectVoice1; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + if(allDirectVoice2 > allBestScore) + { + allBestScore = allDirectVoice2; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return allBestScore; + case BASE_ONLY: + float baseData = 0; + float baseVoice = 0; + + for(int x = 0; x < 24; x++) + { + symbol = mSymbols[mSymbolPointer + x]; + baseData += BASE_DATA[x] * symbol; + baseVoice += BASE_VOICE[x] * symbol; + } + + if(baseData > baseVoice) + { + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + return baseData; + } + else + { + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + return baseVoice; + } + case MOBILE_ONLY: + float mobileData = 0; + float mobileVoice = 0; + + for(int x = 0; x < 24; x++) + { + symbol = mSymbols[mSymbolPointer + x]; + mobileData += MOBILE_DATA[x] * symbol; + mobileVoice += MOBILE_VOICE[x] * symbol; + } + + float mobileBestScore = mobileVoice; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + + if(mobileData > mobileBestScore) + { + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + mobileBestScore = mobileData; + } + + return mobileBestScore; + case DIRECT_ONLY: + float directData1 = 0; + float directData2 = 0; + float directVoice1 = 0; + float directVoice2 = 0; + + for(int x = 0; x < 24; x++) + { + symbol = mSymbols[mSymbolPointer + x]; + directData1 += DIRECT_DATA_1[x] * symbol; + directData2 += DIRECT_DATA_2[x] * symbol; + directVoice1 += DIRECT_VOICE_1[x] * symbol; + directVoice2 += DIRECT_VOICE_2[x] * symbol; + } + + float directBestScore = directData1; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + + if(directData2 > directBestScore) + { + directBestScore = directData2; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + if(directVoice1 > directBestScore) + { + directBestScore = directVoice1; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + if(directVoice2 > directBestScore) + { + directBestScore = directVoice2; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return directBestScore; + default: + throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector128.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector128.java new file mode 100644 index 00000000..b1f7fc06 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector128.java @@ -0,0 +1,395 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * SIMD Vector 128 implementation of DMR Soft Sync Detector. + */ +public class DMRSoftSyncDetectorVector128 extends DMRSoftSyncDetector +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128; + + /** + * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft + * symbols that include this recent soft symbol. + * + * @return correlation score. + */ + public float calculate() + { + FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer); + FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 4); + FloatVector symbols3 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8); + FloatVector symbols4 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 12); + FloatVector symbols5 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 16); + FloatVector symbols6 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 20); + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + FloatVector sync1, sync2, sync3, sync4, sync5, sync6; + float bestScore, candidate; + + switch(mMode) + { + case AUTOMATIC: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + case BASE_ONLY: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + return bestScore; + case MOBILE_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + return bestScore; + case DIRECT_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + default: + throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java new file mode 100644 index 00000000..b01f5edd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java @@ -0,0 +1,296 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * SIMD Vector 256 implementation of DMR Soft Sync Detector. + */ +public class DMRSoftSyncDetectorVector256 extends DMRSoftSyncDetector +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256; + + /** + * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft + * symbols that include this recent soft symbol. + * + * @return correlation score. + */ + public float calculate() + { + FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer); + FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8); + FloatVector symbols3 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 16); + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + FloatVector sync1, sync2, sync3; + float bestScore, candidate; + + switch(mMode) + { + case AUTOMATIC: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + case BASE_ONLY: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + return bestScore; + case MOBILE_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + return bestScore; + case DIRECT_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + default: + throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector512.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector512.java new file mode 100644 index 00000000..e7be109b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector512.java @@ -0,0 +1,270 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * SIMD Vector 512 implementation of DMR Soft Sync Detector. + */ +public class DMRSoftSyncDetectorVector512 extends DMRSoftSyncDetector +{ + private static final boolean[] VECTOR_MASK = new boolean[]{false, false, false, false, false, false, false, false, + true, true, true, true, true, true, true, true}; + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512; + + /** + * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft + * symbols that include this recent soft symbol. + * + * @return correlation score. + */ + public float calculate() + { + //There are 24 symbols that we load into 2x vectors of 16. We load the first 16 symbols into vector 1 and the + //remaining 8 symbols into vector 2 using a vector mask that ignores the first 8 symbols and loads the second 8 + // symbols. This sets the first 8 symbols to zero and those values are ignored during calculations. + VectorMask mask = VectorMask.fromArray(VECTOR_SPECIES, VECTOR_MASK, 0); + FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer); + FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8, mask); + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + FloatVector sync1, sync2; + float bestScore, candidate; + + switch(mMode) + { + case AUTOMATIC: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + case BASE_ONLY: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + return bestScore; + case MOBILE_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + return bestScore; + case DIRECT_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + default: + throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector64.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector64.java new file mode 100644 index 00000000..9689ec5f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector64.java @@ -0,0 +1,593 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +/** + * SIMD Vector 64 implementation of DMR Soft Sync Detector. + */ +public class DMRSoftSyncDetectorVector64 extends DMRSoftSyncDetector +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64; + + /** + * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft + * symbols that include this recent soft symbol. + * + * @return correlation score. + */ + public float calculate() + { + FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer); + FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 2); + FloatVector symbols3 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 4); + FloatVector symbols4 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 6); + FloatVector symbols5 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8); + FloatVector symbols6 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 10); + FloatVector symbols7 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 12); + FloatVector symbols8 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 14); + FloatVector symbols9 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 16); + FloatVector symbols10 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 18); + FloatVector symbols11 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 20); + FloatVector symbols12 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 22); + FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES); + FloatVector sync1, sync2, sync3, sync4, sync5, sync6, sync7, sync8, sync9, sync10, sync11, sync12; + float bestScore, candidate; + + switch(mMode) + { + case AUTOMATIC: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + case BASE_ONLY: + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE; + } + + return bestScore; + case MOBILE_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE; + } + + return bestScore; + case DIRECT_ONLY: + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + bestScore = accumulator.reduceLanes(VectorOperators.ADD); + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1; + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1; + } + + accumulator = FloatVector.zero(VECTOR_SPECIES); + sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0); + sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 2); + sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4); + sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 6); + sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8); + sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 10); + sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12); + sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 14); + sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16); + sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 18); + sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20); + sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 22); + + accumulator = symbols1.fma(sync1, accumulator); + accumulator = symbols2.fma(sync2, accumulator); + accumulator = symbols3.fma(sync3, accumulator); + accumulator = symbols4.fma(sync4, accumulator); + accumulator = symbols5.fma(sync5, accumulator); + accumulator = symbols6.fma(sync6, accumulator); + accumulator = symbols7.fma(sync7, accumulator); + accumulator = symbols8.fma(sync8, accumulator); + accumulator = symbols9.fma(sync9, accumulator); + accumulator = symbols10.fma(sync10, accumulator); + accumulator = symbols11.fma(sync11, accumulator); + accumulator = symbols12.fma(sync12, accumulator); + candidate = accumulator.reduceLanes(VectorOperators.ADD); + + if(candidate > bestScore) + { + bestScore = candidate; + mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2; + } + + return bestScore; + default: + throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetectMode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetectMode.java new file mode 100644 index 00000000..96f58e96 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetectMode.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +/** + * DMR sync detection modes enumeration. + */ +public enum DMRSyncDetectMode +{ + AUTOMATIC, BASE_ONLY, MOBILE_ONLY, DIRECT_ONLY; +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetector.java new file mode 100644 index 00000000..71515244 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetector.java @@ -0,0 +1,48 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +/** + * Base implementation of DMR sync detector that uses sync detection modes to activate or deactivate groups of DMR + * sync patterns. The default mode is AUTOMATIC to monitor for all sync patterns. Additional modes can be used to + * limit sync monitoring for smaller groups of sync patterns for base station, mobile stations and direct mode. + */ +public abstract class DMRSyncDetector +{ + protected DMRSyncDetectMode mMode = DMRSyncDetectMode.AUTOMATIC; + protected DMRSyncPattern mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA; + + /** + * Sets the operating mode for this detector. + * @param mode of sync detection. + */ + public void setMode(DMRSyncDetectMode mode) + { + mMode = mode; + } + + /** + * Current mode of operation + */ + public DMRSyncDetectMode getMode() + { + return mMode; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncModeMonitor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncModeMonitor.java new file mode 100644 index 00000000..ab76373a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncModeMonitor.java @@ -0,0 +1,115 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import java.util.ArrayList; +import java.util.List; + +/** + * Monitors sync pattern detections to automatically detect the prevailing or dominant sync mode group used on a channel + * and optimizes the DMR sync detectors to reduce the quantity of false detects of unused sync patterns and the overall + * sync detection processing workload. + */ +public class DMRSyncModeMonitor +{ + private static final int DOMINANT_THRESHOLD = 10; + private final List mSyncDetectors = new ArrayList<>(); + private int mBaseCount; + private int mMobileCount; + private int mDirectCount; + private DMRSyncDetectMode mMode = DMRSyncDetectMode.AUTOMATIC; + + /** + * Constructs an instance + */ + public DMRSyncModeMonitor() + { + } + + /** + * Sets (and locks) the sync detection mode. This can be directly invoked when the operating mode is known + * in advance, such as for a repeated traffic channel where the expected mode is BASE_ONLY, otherwise this method + * is automatically invoked by this monitor when operating in ALL mode, once the dominant mode is established. + * @param mode to set + */ + public void setMode(DMRSyncDetectMode mode) + { + mMode = mode; + + for(DMRSyncDetector detector : mSyncDetectors) + { + detector.setMode(mode); + } + } + + /** + * Indicates a sync pattern is positively detected and updates the current sync detection mode for all monitored + * sync detectors once the dominant mode is established. + * @param pattern that was detected. + */ + public void detected(DMRSyncPattern pattern) + { + if(mMode != DMRSyncDetectMode.AUTOMATIC) + { + return; + } + + switch(pattern) + { + case BASE_STATION_DATA: + case BASE_STATION_VOICE: + mBaseCount++; + break; + case MOBILE_STATION_DATA: + case MOBILE_STATION_VOICE: + case REVERSE_CHANNEL: + mMobileCount++; + break; + case DIRECT_DATA_TIMESLOT_1: + case DIRECT_DATA_TIMESLOT_2: + case DIRECT_VOICE_TIMESLOT_1: + case DIRECT_VOICE_TIMESLOT_2: + mDirectCount++; + break; + } + + if(mBaseCount - mMobileCount > DOMINANT_THRESHOLD && mBaseCount - mDirectCount > DOMINANT_THRESHOLD) + { + setMode(DMRSyncDetectMode.BASE_ONLY); + } + else if(mMobileCount - mBaseCount > DOMINANT_THRESHOLD && mMobileCount - mDirectCount > DOMINANT_THRESHOLD) + { + setMode(DMRSyncDetectMode.MOBILE_ONLY); + } + else if(mDirectCount - mBaseCount > DOMINANT_THRESHOLD && mDirectCount - mMobileCount > DOMINANT_THRESHOLD) + { + setMode(DMRSyncDetectMode.DIRECT_ONLY); + } + } + + /** + * Adds a sync detector to be managed by this instance. + * @param syncDetector to add + */ + public void add(DMRSyncDetector syncDetector) + { + mSyncDetectors.add(syncDetector); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncPattern.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncPattern.java new file mode 100644 index 00000000..b6b90f3d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncPattern.java @@ -0,0 +1,291 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dmr.sync; + +import io.github.dsheirer.dsp.symbol.Dibit; +import java.util.EnumSet; + +/** + * DMR burst sync patterns enumeration. + * + * Note: in addition to the ETSI-defined patterns, there are additional entries that are used as placeholder sync + * patterns for tracking bursts across voice super frames and for tracking empty timeslots for direct mode. + */ +public enum DMRSyncPattern +{ + BASE_STATION_DATA(0xDFF57D75DF5DL, "BS DATA"), + BASE_STATION_VOICE(0x755FD7DF75F7L, "BS VOICE A"), + + //These are used to identify the sync-less sub frames of a voice super frame + BS_VOICE_FRAME_B("BS VOICE B"), + BS_VOICE_FRAME_C("BS VOICE C"), + BS_VOICE_FRAME_D("BS VOICE D"), + BS_VOICE_FRAME_E("BS VOICE E"), + BS_VOICE_FRAME_F("BS VOICE F"), + + MOBILE_STATION_DATA(0xD5D7F77FD757L, "MS DATA"), + MOBILE_STATION_VOICE(0x7F7D5DD57DFDL, "MS VOICE A"), + REVERSE_CHANNEL(0x77D55F7DFD77L, "MS REVERSE"), + + //These are used to identify the sync-less sub frames of a voice super frame + MS_VOICE_FRAME_B("MS VOICE B"), + MS_VOICE_FRAME_C("MS VOICE C"), + MS_VOICE_FRAME_D("MS VOICE D"), + MS_VOICE_FRAME_E("MS VOICE E"), + MS_VOICE_FRAME_F("MS VOICE F"), + + DIRECT_EMPTY_TIMESLOT("DIRECT EMPTY TIMESLOT"), + + DIRECT_DATA_TIMESLOT_1(0xF7FDD5DDFD55L, "DIRECT DATA TS 1"), + DIRECT_DATA_TIMESLOT_2(0xD7557F5FF7F5L, "DIRECT DATA TS 2"), + DIRECT_VOICE_TIMESLOT_1(0x5D577F7757FFL, "DIRECT VOICE A TS 1"), + DIRECT_VOICE_TIMESLOT_2(0x7DFFD5F55D5FL, "DIRECT VOICE A TS 2"), + + //These are used to identify the sync-less sub frames of a voice super frame + DIRECT_VOICE_FRAME_B("DIRECT VOICE B"), + DIRECT_VOICE_FRAME_C("DIRECT VOICE C"), + DIRECT_VOICE_FRAME_D("DIRECT VOICE D"), + DIRECT_VOICE_FRAME_E("DIRECT VOICE E"), + DIRECT_VOICE_FRAME_F("DIRECT VOICE F"), + + RESERVED(0xDD7FF5D757DDl, "RESERVED"), + UNKNOWN("UNKNOWN"); + + private long mPattern; + private String mLabel; + + /** + * DMR Sync Patterns. See TS 102-361-1, paragraph 9.1.1 + */ + DMRSyncPattern(long pattern, String label) + { + mPattern = pattern; + mLabel = label; + } + + /** + * Alternate constructor for syncs that don't have defined patterns. + */ + DMRSyncPattern(String label) + { + mLabel = label; + } + + //Valid sync patterns (excluding the sync-less voice patterns) + public static final EnumSet SYNC_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE, + MOBILE_STATION_DATA, MOBILE_STATION_VOICE, DIRECT_DATA_TIMESLOT_1, DIRECT_DATA_TIMESLOT_2, + DIRECT_VOICE_TIMESLOT_1, DIRECT_VOICE_TIMESLOT_2); + + //Sync patterns containing a Common Announcement Channel (CACH) + private static final EnumSet CACH_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE, + BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F); + + //Direct Mode TS 1 Sync Patterns + private static final EnumSet DIRECT_MODE_TS1_PATTERNS = EnumSet.of(DIRECT_VOICE_TIMESLOT_1, + DIRECT_DATA_TIMESLOT_1); + + //Direct Mode TS 2 Sync Patterns + private static final EnumSet DIRECT_MODE_TS2_PATTERNS = EnumSet.of(DIRECT_VOICE_TIMESLOT_2, + DIRECT_DATA_TIMESLOT_2); + + //Direct Mode Pseudo Voice Frame Patterns + private static final EnumSet DIRECT_MODE_VOICE_PATTERNS = EnumSet.range(DIRECT_VOICE_FRAME_B, + DIRECT_VOICE_FRAME_F); + + //Mobile Station Sync Patterns + private static final EnumSet MOBILE_STATION_PATTERNS = EnumSet.of(MOBILE_STATION_DATA, + MOBILE_STATION_VOICE, MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, + MS_VOICE_FRAME_F); + + //Voice frame patterns + private static final EnumSet VOICE_PATTERNS = EnumSet.of(BASE_STATION_VOICE, BS_VOICE_FRAME_B, + BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F, MOBILE_STATION_VOICE, + MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F, + DIRECT_VOICE_TIMESLOT_1, DIRECT_VOICE_TIMESLOT_2, DIRECT_EMPTY_TIMESLOT, DIRECT_VOICE_FRAME_B, + DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E, DIRECT_VOICE_FRAME_F); + + /** + * Pattern that represents the enum entry + */ + public long getPattern() + { + return mPattern; + } + + /** + * Indicates if a message using this sync pattern contains a Common Announcement Channel (CACH). + */ + public boolean hasCACH() + { + return CACH_PATTERNS.contains(this); + } + + /** + * Indicates if this is a Direct mode sync pattern, either timeslot 1 or 2. + */ + public boolean isDirect() + { + return isDirectTS1() || isDirectTS2() || isDirectVoice(); + } + + /** + * Indicates if this is Direct Mode sync pattern for timeslot 1 + * @return true if Direct Mode TS1 + */ + public boolean isDirectTS1() + { + return DIRECT_MODE_TS1_PATTERNS.contains(this); + } + + /** + * Indicates if this is Direct Mode sync pattern for timeslot 2 + * @return true if Direct Mode TS2 + */ + public boolean isDirectTS2() + { + return DIRECT_MODE_TS2_PATTERNS.contains(this); + } + + /** + * Indicates if this is Direct Mode voice sync pattern + * @return true if Direct Mode Voice + */ + public boolean isDirectVoice() + { + return DIRECT_MODE_VOICE_PATTERNS.contains(this); + } + + /** + * Indicates if this is a mobile station sync pattern + * @return true if a mobile station sync pattern. + */ + public boolean isMobileStationSyncPattern() + { + return MOBILE_STATION_PATTERNS.contains(this); + } + + /** + * Indicates if this is a mobile sync pattern, either mobile station or direct mode. + * @return true if a mobile sync pattern. + */ + public boolean isMobileSyncPattern() + { + return isMobileStationSyncPattern() || isDirect(); + } + + /** + * Indicates if the current sync pattern is a VOICE pattern. + * @return true if a voice sync pattern. + */ + public boolean isVoicePattern() + { + return VOICE_PATTERNS.contains(this); + } + + /** + * Determines the next voice sync pattern that follows the argument pattern. + * @param pattern for the current burst + * @return pattern for the next burst, or UNKNOWN + */ + public static DMRSyncPattern getNextVoice(DMRSyncPattern pattern) + { + return switch (pattern) + { + case BASE_STATION_VOICE -> BS_VOICE_FRAME_B; + case BS_VOICE_FRAME_B -> BS_VOICE_FRAME_C; + case BS_VOICE_FRAME_C -> BS_VOICE_FRAME_D; + case BS_VOICE_FRAME_D -> BS_VOICE_FRAME_E; + case BS_VOICE_FRAME_E -> BS_VOICE_FRAME_F; + + case MOBILE_STATION_VOICE -> MS_VOICE_FRAME_B; + case MS_VOICE_FRAME_B -> MS_VOICE_FRAME_C; + case MS_VOICE_FRAME_C -> MS_VOICE_FRAME_D; + case MS_VOICE_FRAME_D -> MS_VOICE_FRAME_E; + case MS_VOICE_FRAME_E -> MS_VOICE_FRAME_F; + + case DIRECT_VOICE_TIMESLOT_1, DIRECT_VOICE_TIMESLOT_2 -> DIRECT_VOICE_FRAME_B; + case DIRECT_VOICE_FRAME_B -> DIRECT_VOICE_FRAME_C; + case DIRECT_VOICE_FRAME_C -> DIRECT_VOICE_FRAME_D; + case DIRECT_VOICE_FRAME_D -> DIRECT_VOICE_FRAME_E; + case DIRECT_VOICE_FRAME_E -> DIRECT_VOICE_FRAME_F; + + //Empty slot pattern remains the same to continue accounting for the empty timeslot. + case DIRECT_EMPTY_TIMESLOT -> DIRECT_EMPTY_TIMESLOT; + + default -> UNKNOWN; + }; + } + + /** + * String representation of the sync pattern + */ + public String toString() + { + return mLabel; + } + + /** + * Converts the DMR sync pattern to a float array of ideal phase values for each Dibit to use for correlation against + * a stream of transmitted symbol phases. + * @return symbols array representing the sync pattern. + */ + public float[] toSymbols() + { + float[] symbols = new float[24]; + Dibit[] dibits = toDibits(); + + for(int x = 0; x < 24; x++) + { + symbols[x] = dibits[x].getIdealPhase(); + } + + return symbols; + } + + /** + * Converts the DMR sync pattern to an array of dibit symbols. Note: only the +3 and -3 dibit symbols are used in + * sync patterns. + * @return dibit symbols array representing the sync pattern. + */ + public Dibit[] toDibits() + { + Dibit[] dibits = new Dibit[24]; + long value = getPattern(); + long mask = 3; + long dibitValue; + + for(int x = 0; x < 24; x++) + { + dibitValue = (value & mask) >> (2 * x); + + if(dibitValue == 1) + { + dibits[23 - x] = Dibit.D01_PLUS_3; + } + else + { + dibits[23 - x] = Dibit.D11_MINUS_3; + } + + mask = mask << 2; + } + + return dibits; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java index 72a62b6e..2771474f 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,12 +19,11 @@ 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Abstract calibration plugin base class @@ -123,6 +122,24 @@ public abstract class Calibration return samples; } + /** + * Generates an array of floating point samples in the range 0.0 - 1.0 + * @param size of array + * @return generated samples + */ + protected float[] getPositiveFloatSamples(int size) + { + Random random = new Random(); + + float[] samples = new float[size]; + for(int x = 0; x < samples.length; x++) + { + samples[x] = random.nextFloat(); + } + + return samples; + } + /** * Generates an array of floating point samples in the range -1.0 - 1.0 * @param size of array diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java index fea304b1..fc8b35f6 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java @@ -25,6 +25,7 @@ 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.DqpskDemodulatorCalibration; import io.github.dsheirer.vector.calibrate.demodulator.FmDemodulatorCalibration; import io.github.dsheirer.vector.calibrate.filter.FirFilterCalibration; import io.github.dsheirer.vector.calibrate.filter.RealDcRemovalCalibration; @@ -35,10 +36,12 @@ import io.github.dsheirer.vector.calibrate.filter.RealHalfBand63TapFilterCalibra 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.interpolator.InterpolatorCalibration; import io.github.dsheirer.vector.calibrate.magnitude.MagnitudeCalibration; 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.sync.DMRSoftSyncCalibration; import io.github.dsheirer.vector.calibrate.window.WindowCalibration; import java.util.ArrayList; import java.util.Collections; @@ -104,8 +107,11 @@ public class CalibrationManager sInstance.add(new ComplexGainControlCalibration()); sInstance.add(new ComplexOscillatorCalibration()); sInstance.add(new ComplexMixerCalibration()); + sInstance.add(new DMRSoftSyncCalibration()); + sInstance.add(new DqpskDemodulatorCalibration()); sInstance.add(new FirFilterCalibration()); sInstance.add(new FmDemodulatorCalibration()); + sInstance.add(new InterpolatorCalibration()); sInstance.add(new MagnitudeCalibration()); sInstance.add(new RealDcRemovalCalibration()); sInstance.add(new RealHalfBand11TapFilterCalibration()); @@ -114,7 +120,6 @@ public class CalibrationManager sInstance.add(new RealHalfBand63TapFilterCalibration()); sInstance.add(new RealHalfBandDefaultFilterCalibration()); sInstance.add(new RealOscillatorCalibration()); - // sInstance.add(new HilbertCalibration()); //Not currently used sInstance.add(new WindowCalibration()); //Not currently used } @@ -275,20 +280,8 @@ public class CalibrationManager public static void main(String[] args) { CalibrationManager manager = getInstance(); - - StringBuilder sb = new StringBuilder(); - sb.append("Current Calibration Settings:\n"); - for(CalibrationType type: CalibrationType.values()) - { - Calibration calibration = manager.getCalibration(type); - if(calibration != null) - { - sb.append("\t").append(type.name()).append("\t").append(manager.getCalibration(type).getImplementation()).append("\n"); - } - } - System.out.println(sb); - - manager.reset(); +// manager.reset(); + manager.reset(CalibrationType.DQPSK_DEMODULATOR); if(!manager.isCalibrated()) { diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java index edab2d4d..a08fff71 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +37,8 @@ public enum CalibrationType AIRSPY_UNPACKED_ITERATOR("Airspy Unpacked Iterator", 1), AM_DEMODULATOR("AM Demodulator", 1), DC_REMOVAL_REAL("Real DC Removal Filter", 1), + DMR_SOFT_SYNC_DETECTOR("DMR Soft Sync Detector", 1), + DQPSK_DEMODULATOR("DQPSK Demodulator", 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), @@ -46,6 +48,7 @@ public enum CalibrationType FM_DEMODULATOR("FM Demodulator", 2), GAIN_CONTROL_COMPLEX("Complex Gain Control", 1), HILBERT_TRANSFORM("Hilbert Transform", 1), + INTERPOLATOR("Interpolator", 1), MAGNITUDE("Magnitude", 1), MIXER_COMPLEX("Complex Mixer", 1), OSCILLATOR_REAL("Real Oscillator", 1), diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java new file mode 100644 index 00000000..faa973ee --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java @@ -0,0 +1,287 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.psk.dqpsk.DQPSKDemodulator; +import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorScalar; +import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector128; +import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector256; +import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector512; +import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector64; +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 DQPSK demodulator options + */ +public class DqpskDemodulatorCalibration 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 DQPSKDemodulator mScalarDemodulator = new DQPSKDemodulatorScalar(50000.0, 4800); + private final DQPSKDemodulator mVectorDemodulator64 = new DQPSKDemodulatorVector64(50000.0, 4800); + private final DQPSKDemodulator mVectorDemodulator128 = new DQPSKDemodulatorVector128(50000.0, 4800); + private final DQPSKDemodulator mVectorDemodulator256 = new DQPSKDemodulatorVector256(50000.0, 4800); + private final DQPSKDemodulator mVectorDemodulator512 = new DQPSKDemodulatorVector512(50000.0, 4800); + + /** + * Constructs an instance + */ + public DqpskDemodulatorCalibration() + { + super(CalibrationType.DQPSK_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("DQPSK DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean64 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(i, q); + vectorMean64.increment(score); + } + + mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult())); + + Mean vectorMean128 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(i, q); + vectorMean128.increment(score); + } + + mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult())); + + Mean vectorMean256 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(i, q); + vectorMean256.increment(score); + } + + mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult())); + + Mean vectorMean512 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(i, q); + vectorMean512.increment(score); + } + + mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult())); + + //Start tests + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(i, q); + scalarMean.increment(score); + } + + mLog.info("DQPSK DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean64.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(i, q); + vectorMean64.increment(score); + } + + mLog.info("DQPSK DEMODULATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult())); + + vectorMean128.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(i, q); + vectorMean128.increment(score); + } + + mLog.info("DQPSK DEMODULATOR - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult())); + + vectorMean256.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(i, q); + vectorMean256.increment(score); + } + + mLog.info("DQPSK DEMODULATOR - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult())); + + vectorMean512.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(i, q); + vectorMean512.increment(score); + } + + mLog.info("DQPSK DEMODULATOR - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult())); + + Implementation best = Implementation.SCALAR; + double bestScore = scalarMean.getResult(); + + if(vectorMean64.getResult() > bestScore) + { + bestScore = vectorMean64.getResult(); + best = Implementation.VECTOR_SIMD_64; + } + + if(vectorMean128.getResult() > bestScore) + { + bestScore = vectorMean128.getResult(); + best = Implementation.VECTOR_SIMD_128; + } + + if(vectorMean256.getResult() > bestScore) + { + bestScore = vectorMean256.getResult(); + best = Implementation.VECTOR_SIMD_256; + } + + if(vectorMean512.getResult() > bestScore) + { + best = Implementation.VECTOR_SIMD_512; + } + + setImplementation(best); + + mLog.info("DQPSK DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] i, float[] q) + { + long count = 0; + float accumulator = 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 testVector64(float[] i, float[] q) + { + long count = 0; + float accumulator = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVectorDemodulator64.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] i, float[] q) + { + long count = 0; + float accumulator = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVectorDemodulator128.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] i, float[] q) + { + long count = 0; + float accumulator = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVectorDemodulator256.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector512(float[] i, float[] q) + { + long count = 0; + float accumulator = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + float[] demodulated = mVectorDemodulator512.demodulate(i, q); + accumulator += demodulated[1]; + count++; + } + + return count + (long)(accumulator * 0); + } + + public static void main(String[] args) + { + DqpskDemodulatorCalibration calibration = new DqpskDemodulatorCalibration(); + + try + { + calibration.calibrate(); + } + catch(Exception e) + { + mLog.error("Error during calibration", e); + } + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/interpolator/InterpolatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/interpolator/InterpolatorCalibration.java new file mode 100644 index 00000000..2a6f944c --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/interpolator/InterpolatorCalibration.java @@ -0,0 +1,268 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.interpolator; + +import io.github.dsheirer.dsp.filter.interpolator.Interpolator; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorScalar; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorVector128; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorVector256; +import io.github.dsheirer.dsp.filter.interpolator.InterpolatorVector64; +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 InterpolatorCalibration extends Calibration +{ + private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final int BUFFER_SIZE = 8; + private static final int ITERATION_DURATION_MS = 1000; + private static final int WARMUP_ITERATIONS = 5; + private static final int TEST_ITERATIONS = 5; + + private Interpolator mScalar = new InterpolatorScalar(); + private Interpolator mVector256 = new InterpolatorVector256(); + private Interpolator mVector128 = new InterpolatorVector128(); + private Interpolator mVector64 = new InterpolatorVector64(); + + /** + * Constructs an instance + */ + public InterpolatorCalibration() + { + super(CalibrationType.INTERPOLATOR); + } + + /** + * Performs calibration to determine optimal (Scalar vs Vector) operation type. + * @throws CalibrationException + */ + @Override public void calibrate() throws CalibrationException + { + float[] samples = getFloatSamples(BUFFER_SIZE); + float[] interpolationPoints = getPositiveFloatSamples(2048); + + Mean scalarMean = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testScalar(samples, interpolationPoints); + scalarMean.increment(score); + } + + mLog.info("INTERPOLATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vector256Mean = new Mean(); + + if(VECTOR_SPECIES.length() >= 8) + { + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples, interpolationPoints); + vector256Mean.increment(score); + } + + mLog.info("INTERPOLATOR 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, interpolationPoints); + vector128Mean.increment(score); + } + + mLog.info("INTERPOLATOR 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, interpolationPoints); + vector64Mean.increment(score); + } + + mLog.info("INTERPOLATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + } + + //Test starts ... + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples, interpolationPoints); + scalarMean.increment(score); + } + + mLog.info("INTERPOLATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + double bestScore = scalarMean.getResult(); + setImplementation(Implementation.SCALAR); + + if(VECTOR_SPECIES.length() >= 8) + { + vector256Mean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples, interpolationPoints); + vector256Mean.increment(score); + } + + mLog.info("INTERPOLATOR - 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, interpolationPoints); + vector128Mean.increment(score); + } + + mLog.info("INTERPOLATOR - 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, interpolationPoints); + vector64Mean.increment(score); + } + + mLog.info("INTERPOLATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult())); + + if(vector64Mean.getResult() > bestScore) + { + setImplementation(Implementation.VECTOR_SIMD_64); + } + } + + mLog.info("INTERPOLATOR - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation()); + } + + private long testScalar(float[] samples, float[] interpolationPoints) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(int x = 0; x < interpolationPoints.length; x++) + { + accumulator += mScalar.filter(samples, 0, interpolationPoints[x]); + } + + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector64(float[] samples, float[] interpolationPoints) + { + double accumulator = 0.0f; + + long start = System.currentTimeMillis(); + long count = 0; + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(int x = 0; x < interpolationPoints.length; x++) + { + accumulator += mVector64.filter(samples, 0, interpolationPoints[x]); + } + + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector128(float[] samples, float[] interpolationPoints) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(int x = 0; x < interpolationPoints.length; x++) + { + accumulator += mVector128.filter(samples, 0, interpolationPoints[x]); + } + + count++; + } + + return count + (long)(accumulator * 0); + } + + private long testVector256(float[] samples, float[] interpolationPoints) + { + double accumulator = 0.0f; + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(int x = 0; x < interpolationPoints.length; x++) + { + accumulator += mVector256.filter(samples, 0, interpolationPoints[x]); + } + count++; + } + + return count + (long)(accumulator * 0); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sync/DMRSoftSyncCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sync/DMRSoftSyncCalibration.java new file mode 100644 index 00000000..bed91710 --- /dev/null +++ b/src/main/java/io/github/dsheirer/vector/calibrate/sync/DMRSoftSyncCalibration.java @@ -0,0 +1,292 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.sync; + +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorScalar; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector128; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector256; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector512; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector64; +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; + +/** + * DMR Soft Sync Detector calibration + */ +public class DMRSoftSyncCalibration 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 final DMRSoftSyncDetector mScalarDetector = new DMRSoftSyncDetectorScalar(); + private final DMRSoftSyncDetector mVectorDetector64 = new DMRSoftSyncDetectorVector64(); + private final DMRSoftSyncDetector mVectorDetector128 = new DMRSoftSyncDetectorVector128(); + private final DMRSoftSyncDetector mVectorDetector256 = new DMRSoftSyncDetectorVector256(); + private final DMRSoftSyncDetector mVectorDetector512 = new DMRSoftSyncDetectorVector512(); + + /** + * Constructs an instance + */ + public DMRSoftSyncCalibration() + { + super(CalibrationType.DMR_SOFT_SYNC_DETECTOR); + } + + @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("DMR SOFT SYNC DETECTOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + Mean vectorMean64 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector64(samples); + vectorMean64.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult())); + + Mean vectorMean128 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector128(samples); + vectorMean128.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult())); + + Mean vectorMean256 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector256(samples); + vectorMean256.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult())); + + Mean vectorMean512 = new Mean(); + + for(int x = 0; x < WARMUP_ITERATIONS; x++) + { + long score = testVector512(samples); + vectorMean512.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult())); + + //Start tests + scalarMean.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testScalar(samples); + scalarMean.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult())); + + vectorMean64.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector64(samples); + vectorMean64.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult())); + + vectorMean128.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector128(samples); + vectorMean128.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult())); + + vectorMean256.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector256(samples); + vectorMean256.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult())); + + vectorMean512.clear(); + + for(int x = 0; x < TEST_ITERATIONS; x++) + { + long score = testVector512(samples); + vectorMean512.increment(score); + } + + mLog.info("DMR SOFT SYNC DETECTOR - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult())); + + Implementation best = Implementation.SCALAR; + double bestScore = scalarMean.getResult(); + + if(vectorMean64.getResult() > bestScore) + { + bestScore = vectorMean64.getResult(); + best = Implementation.VECTOR_SIMD_64; + } + + if(vectorMean128.getResult() > bestScore) + { + bestScore = vectorMean128.getResult(); + best = Implementation.VECTOR_SIMD_128; + } + + if(vectorMean256.getResult() > bestScore) + { + bestScore = vectorMean256.getResult(); + best = Implementation.VECTOR_SIMD_256; + } + + if(vectorMean512.getResult() > bestScore) + { + best = Implementation.VECTOR_SIMD_512; + } + + setImplementation(best); + + mLog.info("DMR SOFT SYNC DETECTOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation()); + } + + private long testScalar(float[] samples) + { + long count = 0; + + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(float sample : samples) + { + mScalarDetector.process(sample); + } + count++; + } + + return count; + } + + private long testVector64(float[] samples) + { + long count = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(float sample : samples) + { + mVectorDetector64.process(sample); + } + count++; + } + + return count; + } + + private long testVector128(float[] samples) + { + long count = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(float sample : samples) + { + mVectorDetector128.process(sample); + } + count++; + } + + return count; + } + + private long testVector256(float[] samples) + { + long count = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(float sample : samples) + { + mVectorDetector256.process(sample); + } + count++; + } + + return count; + } + + private long testVector512(float[] samples) + { + long count = 0; + long start = System.currentTimeMillis(); + + while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS) + { + for(float sample : samples) + { + mVectorDetector512.process(sample); + } + count++; + } + + return count; + } + + public static void main(String[] args) + { + DMRSoftSyncCalibration calibration = new DMRSoftSyncCalibration(); + + try + { + calibration.calibrate(); + } + catch(Exception e) + { + mLog.error("Error during calibration", e); + } + } +} diff --git a/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java b/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java new file mode 100644 index 00000000..5ad1fb5c --- /dev/null +++ b/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java @@ -0,0 +1,87 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.dqpsk; + +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorScalar; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector128; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector256; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector512; +import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector64; +import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * JUnit tests for the DMR Soft Sync Detector. + */ +public class DmrSoftSyncDetectorTest +{ + private static final float SYMBOL_VALUE = (float)(3.0 * Math.PI / 4.0); + + /** + * Test: feed each sync pattern into each detector implementation and verify that each detector selected the + * correct sync pattern. + * + * Success: all detector implementations correctly detect all sync patterns. + */ + @Test + void testAllSyncPatterns() + { + DMRSoftSyncDetector scalar = new DMRSoftSyncDetectorScalar(); + DMRSoftSyncDetector vector64 = new DMRSoftSyncDetectorVector64(); + DMRSoftSyncDetector vector128 = new DMRSoftSyncDetectorVector128(); + DMRSoftSyncDetector vector256 = new DMRSoftSyncDetectorVector256(); + DMRSoftSyncDetector vector512 = new DMRSoftSyncDetectorVector512(); + + float symbol; + + for(DMRSyncPattern pattern: DMRSyncPattern.SYNC_PATTERNS) + { + for(Dibit dibit: pattern.toDibits()) + { + symbol = toSymbol(dibit); + scalar.process(symbol); + vector64.process(symbol); + vector128.process(symbol); + vector256.process(symbol); + vector512.process(symbol); + } + + assertSame(scalar.getDetectedPattern(), pattern, "Scalar Detector Did Not Correctly Detect Pattern: " + pattern); + assertSame(vector64.getDetectedPattern(), pattern, "Vector 64 Detector Did Not Correctly Detect Pattern: " + pattern); + assertSame(vector128.getDetectedPattern(), pattern, "Vector 128 Detector Did Not Correctly Detect Pattern: " + pattern); + assertSame(vector256.getDetectedPattern(), pattern, "Vector 256 Detector Did Not Correctly Detect Pattern: " + pattern); + assertSame(vector512.getDetectedPattern(), pattern, "Vector 512 Detector Did Not Correctly Detect Pattern: " + pattern); + } + } + + /** + * Converts the dibit to the optimal symbol value + * @param dibit to convert + * @return dibit's symbol value in radians. + */ + private static float toSymbol(Dibit dibit) + { + return dibit == Dibit.D01_PLUS_3 ? SYMBOL_VALUE : -SYMBOL_VALUE; + } +}