diff --git a/EDAC/Trellis.cs b/EDAC/Trellis.cs new file mode 100644 index 0000000..4df288e --- /dev/null +++ b/EDAC/Trellis.cs @@ -0,0 +1,659 @@ +/** +* Digital Voice Modem - Fixed Network Equipment +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Fixed Network Equipment +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +*/ + +using System; + +namespace fnecore.EDAC +{ + /// + /// Implements 1/2 rate and 3/4 rate Trellis for DMR/P25. + /// + public sealed class Trellis + { + private static readonly uint[] INTERLEAVE_TABLE = new uint[98] { + 0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81, 88, 89, 96, 97, + 2, 3, 10, 11, 18, 19, 26, 27, 34, 35, 42, 43, 50, 51, 58, 59, 66, 67, 74, 75, 82, 83, 90, 91, + 4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45, 52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93, + 6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95 + }; + + private static readonly byte[] ENCODE_TABLE_34 = new byte[] { + 0, 8, 4, 12, 2, 10, 6, 14, + 4, 12, 2, 10, 6, 14, 0, 8, + 1, 9, 5, 13, 3, 11, 7, 15, + 5, 13, 3, 11, 7, 15, 1, 9, + 3, 11, 7, 15, 1, 9, 5, 13, + 7, 15, 1, 9, 5, 13, 3, 11, + 2, 10, 6, 14, 0, 8, 4, 12, + 6, 14, 0, 8, 4, 12, 2, 10 + }; + + private static readonly byte[] ENCODE_TABLE_12 = new byte[] { + 0, 15, 12, 3, + 4, 11, 8, 7, + 13, 2, 1, 14, + 9, 6, 5, 10 + }; + + /* + ** Methods + */ + + /// + /// Initializes a new instance of the class. + /// + public Trellis() + { + /* stub */ + } + + /// + /// Decodes 3/4 rate Trellis. + /// + /// Trellis symbol bytes. + /// Output bytes. + /// True, if decoded, otherwise false. + public bool decode34(byte[] data, ref byte[] payload) + { + if (data == null) + throw new NullReferenceException("data"); + if (payload == null) + throw new NullReferenceException("payload"); + + int[] dibits = new int[98U]; + deinterleave(data, ref dibits); + + byte[] points = new byte[49U]; + dibitsToPoints(dibits, ref points); + + // check the original code + byte[] tribits = new byte[49U]; + uint failPos = checkCode34(points, ref tribits); + if (failPos == 999U) { + tribitsToBits(tribits, ref payload); + return true; + } + + byte[] savePoints = new byte[49U]; + for (uint i = 0U; i< 49U; i++) + savePoints[i] = points[i]; + + bool ret = fixCode34(points, failPos, ref payload); + if (ret) + return true; + + if (failPos == 0U) + return false; + + // Backtrack one place for a last go + return fixCode34(savePoints, failPos - 1U, ref payload); + } + + /// + /// Encodes 3/4 rate Trellis. + /// + /// Input bytes. + /// Trellis symbol bytes. + public void encode34(byte[] payload, ref byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + if (payload == null) + throw new NullReferenceException("payload"); + + byte[] tribits = new byte[49U]; + bitsToTribits(payload, ref tribits); + + byte[] points = new byte[49U]; + byte state = 0; + + for (uint i = 0U; i < 49U; i++) + { + byte tribit = tribits[i]; + + points[i] = ENCODE_TABLE_34[state * 8U + tribit]; + + state = tribit; + } + + int[] dibits = new int[98U]; + pointsToDibits(points, ref dibits); + + interleave(dibits, ref data); + } + + /// + /// Decodes 1/2 rate Trellis. + /// + /// Trellis symbol bytes. + /// Output bytes. + /// True, if decoded, otherwise false. + public bool decode12(byte[] data, ref byte[] payload) + { + if (data == null) + throw new NullReferenceException("data"); + if (payload == null) + throw new NullReferenceException("payload"); + + int[] dibits = new int[98U]; + deinterleave(data, ref dibits); + + byte[] points = new byte[49U]; + dibitsToPoints(dibits, ref points); + + // Check the original code + byte[] bits = new byte[49U]; + uint failPos = checkCode12(points, ref bits); + if (failPos == 999U) + { + dibitsToBits(bits, ref payload); + return true; + } + + byte[] savePoints = new byte[49U]; + for (uint i = 0U; i < 49U; i++) + savePoints[i] = points[i]; + + bool ret = fixCode12(points, failPos, ref payload); + if (ret) + return true; + + if (failPos == 0U) + return false; + + // Backtrack one place for a last go + return fixCode12(savePoints, failPos - 1U, ref payload); + } + + /// + /// Encodes 1/2 rate Trellis. + /// + /// Input bytes. + /// Trellis symbol bytes. + public void encode12(byte[] payload, ref byte[] data) + { + if (data == null) + throw new NullReferenceException("data"); + if (payload == null) + throw new NullReferenceException("payload"); + + byte[] bits = new byte[49U]; + bitsToDibits(payload, ref bits); + + byte[] points = new byte[49U]; + byte state = 0; + + for (uint i = 0U; i < 49U; i++) + { + byte bit = bits[i]; + + points[i] = ENCODE_TABLE_12[state * 4U + bit]; + + state = bit; + } + + int[] dibits = new int[98U]; + pointsToDibits(points, ref dibits); + + interleave(dibits, ref data); + } + + /// + /// Helper to deinterleave the input symbols into dibits. + /// + /// Trellis symbol bytes. + /// Dibits. + private void deinterleave(byte[] data, ref int[] dibits) + { + for (uint i = 0U; i < 98U; i++) { + uint n = i * 2U + 0U; + bool b1 = FneUtils.ReadBit(data, n) != false; + + n = i * 2U + 1U; + bool b2 = FneUtils.ReadBit(data, n) != false; + + int dibit; + if (!b1 && b2) + dibit = +3; + else if (!b1 && !b2) + dibit = +1; + else if (b1 && !b2) + dibit = -1; + else + dibit = -3; + + n = INTERLEAVE_TABLE[i]; + dibits[n] = dibit; + } + } + + /// + /// Helper to interleave the input dibits into symbols. + /// + /// Dibits. + /// Trellis symbol bytes. + private void interleave(int[] dibits, ref byte[] data) + { + for (uint i = 0U; i < 98U; i++) { + uint n = INTERLEAVE_TABLE[i]; + + bool b1, b2; + switch (dibits[n]) + { + case +3: + b1 = false; + b2 = true; + break; + case +1: + b1 = false; + b2 = false; + break; + case -1: + b1 = true; + b2 = false; + break; + default: + b1 = true; + b2 = true; + break; + } + + n = i * 2U + 0U; + FneUtils.WriteBit(ref data, n, b1); + + n = i * 2U + 1U; + FneUtils.WriteBit(ref data, n, b2); + } + } + + /// + /// Helper to map dibits to 4FSK constellation points. + /// + /// Dibits. + /// 4FSK constellation points. + private void dibitsToPoints(int[] dibits, ref byte[] points) + { + for (uint i = 0U; i < 49U; i++) { + if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == -1) + points[i] = 0; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == -1) + points[i] = 1; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == -3) + points[i] = 2; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == -3) + points[i] = 3; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == -1) + points[i] = 4; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == -1) + points[i] = 5; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == -3) + points[i] = 6; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == -3) + points[i] = 7; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == +3) + points[i] = 8; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == +3) + points[i] = 9; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == +1) + points[i] = 10; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == +1) + points[i] = 11; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == +3) + points[i] = 12; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == +3) + points[i] = 13; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == +1) + points[i] = 14; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == +1) + points[i] = 15; + } + } + + /// + /// Helper to map 4FSK constellation points to dibits. + /// + /// 4FSK Constellation points. + /// Dibits. + private void pointsToDibits(byte[] points, ref int[] dibits) + { + for (uint i = 0U; i < 49U; i++) { + switch (points[i]) + { + case 0: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = -1; + break; + case 1: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = -1; + break; + case 2: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = -3; + break; + case 3: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = -3; + break; + case 4: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = -1; + break; + case 5: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = -1; + break; + case 6: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = -3; + break; + case 7: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = -3; + break; + case 8: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = +3; + break; + case 9: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = +3; + break; + case 10: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = +1; + break; + case 11: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = +1; + break; + case 12: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = +3; + break; + case 13: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = +3; + break; + case 14: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = +1; + break; + default: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = +1; + break; + } + } + } + + /// + /// Helper to convert a byte payload into tribits. + /// + /// Byte payload. + /// Tribits. + private void bitsToTribits(byte[] payload, ref byte[] tribits) + { + for (uint i = 0U; i < 48U; i++) { + uint n = i * 3U; + + bool b1 = FneUtils.ReadBit(payload, n) != false; + n++; + bool b2 = FneUtils.ReadBit(payload, n) != false; + n++; + bool b3 = FneUtils.ReadBit(payload, n) != false; + + byte tribit = 0; + tribit |= (byte)(b1 ? 4U : 0U); + tribit |= (byte)(b2 ? 2U : 0U); + tribit |= (byte)(b3 ? 1U : 0U); + + tribits[i] = tribit; + } + + tribits[48U] = 0; + } + + /// + /// Helper to convert a byte payload into dibits. + /// + /// Byte payload. + /// Dibits. + private void bitsToDibits(byte[] payload, ref byte[] dibits) + { + for (uint i = 0U; i < 48U; i++) { + uint n = i * 2U; + + bool b1 = FneUtils.ReadBit(payload, n) != false; + n++; + bool b2 = FneUtils.ReadBit(payload, n) != false; + + byte dibit = 0; + dibit |= (byte)(b1 ? 2U : 0U); + dibit |= (byte)(b2 ? 1U : 0U); + + dibits[i] = dibit; + } + + dibits[48U] = 0; + } + + /// + /// Helper to convert tribits into a byte payload. + /// + /// Tribits. + /// Byte payload. + private void tribitsToBits(byte[] tribits, ref byte[] payload) + { + for (uint i = 0U; i < 48U; i++) { + byte tribit = tribits[i]; + + bool b1 = (tribit & 0x04U) == 0x04U; + bool b2 = (tribit & 0x02U) == 0x02U; + bool b3 = (tribit & 0x01U) == 0x01U; + + uint n = i * 3U; + + FneUtils.WriteBit(ref payload, n, b1); + n++; + FneUtils.WriteBit(ref payload, n, b2); + n++; + FneUtils.WriteBit(ref payload, n, b3); + } + } + + /// + /// Helper to convert tribits into a byte payload. + /// + /// Dibits. + /// Byte payload. + private void dibitsToBits(byte[] dibits, ref byte[] payload) + { + for (uint i = 0U; i < 48U; i++) { + byte dibit = dibits[i]; + + bool b1 = (dibit & 0x02U) == 0x02U; + bool b2 = (dibit & 0x01U) == 0x01U; + + uint n = i * 2U; + + FneUtils.WriteBit(ref payload, n, b1); + n++; + FneUtils.WriteBit(ref payload, n, b2); + } + } + + /// + /// Helper to fix errors in Trellis coding. + /// + /// 4FSK constellation points. + /// + /// Byte payload. + /// True, if error corrected, otherwise false. + private bool fixCode34(byte[] points, uint failPos, ref byte[] payload) + { + for (uint j = 0; j < 20; j++) + { + uint bestPos = 0; + byte bestVal = 0; + + for (byte i = 0; i < 16; i++) + { + points[failPos] = i; + + byte[] tribits = new byte[49]; + uint pos = checkCode34(points, ref tribits); + if (pos == 999) + { + tribitsToBits(tribits, ref payload); + return true; + } + + if (pos > bestPos) + { + bestPos = pos; + bestVal = i; + } + } + + points[failPos] = bestVal; + failPos = bestPos; + } + + return false; + } + + /// + /// Helper to detect errors in Trellis coding. + /// + /// 4FSK constellation points. + /// Tribits. + /// + private uint checkCode34(byte[] points, ref byte[] tribits) + { + byte state = 0; + + for (uint i = 0; i < 49; i++) + { + tribits[i] = 9; + + for (byte j = 0; j < 8; j++) + { + if (points[i] == ENCODE_TABLE_34[state * 8 + j]) + { + tribits[i] = j; + break; + } + } + + if (tribits[i] == 9) + return i; + + state = tribits[i]; + } + + if (tribits[48] != 0) + return 48; + + return 999; + } + + + /// + /// Helper to fix errors in Trellis coding. + /// + /// 4FSK constellation points. + /// + /// Byte payload. + /// True, if error corrected, otherwise false. + private bool fixCode12(byte[] points, uint failPos, ref byte[] payload) + { + for (uint j = 0; j < 20; j++) { + uint bestPos = 0; + byte bestVal = 0; + + for (byte i = 0; i < 16; i++) + { + points[failPos] = i; + + byte[] dibits = new byte[49]; + uint pos = checkCode12(points, ref dibits); + if (pos == 999) + { + dibitsToBits(dibits, ref payload); + return true; + } + + if (pos > bestPos) + { + bestPos = pos; + bestVal = i; + } + } + + points[failPos] = bestVal; + failPos = bestPos; + } + + return false; + } + + /// + /// Helper to detect errors in Trellis coding. + /// + /// 4FSK constellation points. + /// Dibits. + /// + private uint checkCode12(byte[] points, ref byte[] dibits) + { + byte state = 0; + + for (uint i = 0; i < 49; i++) + { + dibits[i] = 5; + + for (byte j = 0; j < 4; j++) + { + if (points[i] == ENCODE_TABLE_12[state * 4 + j]) + { + dibits[i] = j; + break; + } + } + + if (dibits[i] == 5) + return i; + + state = dibits[i]; + } + + if (dibits[48] != 0) + return 48; + + return 999; + } + } // public sealed class Trellis +} // namespace fnecore.EDAC