// From W3AXL console
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using fnecore;
using Serilog;
using WhackerLinkLib;
namespace WhackerLinkConsoleV2
{
public enum MBE_MODE
{
DMR_AMBE, //! DMR AMBE
IMBE_88BIT, //! 88-bit IMBE (P25)
}
///
/// Wrapper class for the c++ dvmvocoder encoder library
///
/// Using info from https://stackoverflow.com/a/315064/1842613
public class MBEEncoder
{
///
/// Create a new MBEEncoder
///
///
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr MBEEncoder_Create(MBE_MODE mode);
///
/// Encode PCM16 samples to MBE codeword
///
/// Input PCM samples
/// Output MBE codeword
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern void MBEEncoder_Encode(IntPtr pEncoder, [In] Int16[] samples, [Out] byte[] codeword);
///
/// Encode MBE to bits
///
///
///
///
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern void MBEEncoder_EncodeBits(IntPtr pEncoder, [In] char[] bits, [Out] byte[] codeword);
///
/// Delete a created MBEEncoder
///
///
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern void MBEEncoder_Delete(IntPtr pEncoder);
///
/// Pointer to the encoder instance
///
private IntPtr encoder { get; set; }
///
/// Create a new MBEEncoder instance
///
/// Vocoder Mode (DMR or P25)
public MBEEncoder(MBE_MODE mode)
{
encoder = MBEEncoder_Create(mode);
}
///
/// Private class destructor properly deletes interop instance
///
~MBEEncoder()
{
MBEEncoder_Delete(encoder);
}
///
/// Encode PCM16 samples to MBE codeword
///
///
///
public void encode([In] Int16[] samples, [Out] byte[] codeword)
{
MBEEncoder_Encode(encoder, samples, codeword);
}
public void encodeBits([In] char[] bits, [Out] byte[] codeword)
{
MBEEncoder_EncodeBits(encoder, bits, codeword);
}
}
///
/// Wrapper class for the c++ dvmvocoder decoder library
///
public class MBEDecoder
{
///
/// Create a new MBEDecoder
///
///
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr MBEDecoder_Create(MBE_MODE mode);
///
/// Decode MBE codeword to samples
///
/// Input PCM samples
/// Output MBE codeword
/// Number of decode errors
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 MBEDecoder_Decode(IntPtr pDecoder, [In] byte[] codeword, [Out] Int16[] samples);
///
/// Decode MBE to bits
///
///
///
///
///
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 MBEDecoder_DecodeBits(IntPtr pDecoder, [In] byte[] codeword, [Out] char[] bits);
///
/// Delete a created MBEDecoder
///
///
[DllImport("libvocoder", CallingConvention = CallingConvention.Cdecl)]
public static extern void MBEDecoder_Delete(IntPtr pDecoder);
///
/// Pointer to the decoder instance
///
private IntPtr decoder { get; set; }
///
/// Create a new MBEDecoder instance
///
/// Vocoder Mode (DMR or P25)
public MBEDecoder(MBE_MODE mode)
{
decoder = MBEDecoder_Create(mode);
}
///
/// Private class destructor properly deletes interop instance
///
~MBEDecoder()
{
MBEDecoder_Delete(decoder);
}
///
/// Decode MBE codeword to PCM16 samples
///
///
///
public Int32 decode([In] byte[] codeword, [Out] Int16[] samples)
{
return MBEDecoder_Decode(decoder, codeword, samples);
}
///
/// Decode MBE codeword to bits
///
///
///
///
public Int32 decodeBits([In] byte[] codeword, [Out] char[] bits)
{
return MBEDecoder_DecodeBits(decoder, codeword, bits);
}
}
public static class MBEToneGenerator
{
///
/// Encodes a single tone to an AMBE tone frame
///
///
///
///
///
public static void AmbeEncodeSingleTone(int tone_freq_hz, char tone_amplitude, [Out] byte[] codeword)
{
// U bit vectors
// u0 and u1 are 12 bits
// u2 is 11 bits
// u3 is 14 bits
// total length is 49 bits
ushort[] u = new ushort[4];
// Convert the tone frequency to the nearest tone index
uint tone_idx = (uint)((float)tone_freq_hz / 31.25f);
// Validate tone index
if (tone_idx < 5 || tone_idx > 122)
{
throw new ArgumentOutOfRangeException($"Tone index for frequency out of range!");
}
// Validate amplitude value
if (tone_amplitude > 127)
{
throw new ArgumentOutOfRangeException("Tone amplitude must be between 0 and 127!");
}
// Make sure tone index only has 7 bits (it should but we make sure :) )
tone_idx &= 0b01111111;
// Encode u vectors per TIA-102.BABA-1 section 7.2
// u0[11-6] are always 1 to indicate a tone, so we left-shift 63u (0x00111111) a full byte (8 bits)
u[0] |= (ushort)(63 << 8);
// u0[5-0] are AD (tone amplitude byte) bits 6-1
u[0] |= (ushort)(tone_amplitude >> 1);
// u1[11-4] are tone index bits 7-0 (the full byte)
u[1] |= (ushort)(tone_idx << 4);
// u1[3-0] are tone index bits 7-4
u[1] |= (ushort)(tone_idx >> 4);
// u2[10-7] are tone index bits 3-0
u[2] |= (ushort)((tone_idx & 0b00001111) << 7);
// u2[6-0] are tone index bits 7-1
u[2] |= (ushort)(tone_idx >> 1);
// u3[13] is the last bit of the tone index
u[3] |= (ushort)((tone_idx & 0b1) << 13);
// u3[12-5] is the full tone index byte
u[3] |= (ushort)(tone_idx << 5);
// u3[4] is the last bit of the amplitude byte
u[3] |= (ushort)((tone_amplitude & 0b1) << 4);
// u3[3-0] is always 0 so we don't have to do anything here
// Convert u buffer to byte
Buffer.BlockCopy(u, 0, codeword, 0, 8);
}
///
/// Encode a single tone to an IMBE codeword sequence using a lookup table
///
///
///
public static void IMBEEncodeSingleTone(ushort tone_freq_hz, [Out] byte[] codeword)
{
// Find nearest tone in the lookup table
List tone_keys = VocoderToneLookupTable.IMBEToneFrames.Keys.ToList();
ushort nearest = tone_keys.Aggregate((x, y) => Math.Abs(x - tone_freq_hz) < Math.Abs(y - tone_freq_hz) ? x : y);
byte[] tone_codeword = VocoderToneLookupTable.IMBEToneFrames[nearest];
Array.Copy(tone_codeword, codeword, tone_codeword.Length);
}
}
public class MBEInterleaver
{
public const int PCM_SAMPLES = 160;
public const int AMBE_CODEWORD_SAMPLES = 9;
public const int AMBE_CODEWORD_BITS = 49;
public const int IMBE_CODEWORD_SAMPLES = 11;
public const int IMBE_CODEWORD_BITS = 88;
private MBE_MODE mode;
private MBEEncoder encoder;
private MBEDecoder decoder;
public MBEInterleaver(MBE_MODE mode)
{
this.mode = mode;
encoder = new MBEEncoder(this.mode);
decoder = new MBEDecoder(this.mode);
}
public Int32 Decode([In] byte[] codeword, [Out] byte[] mbeBits)
{
// Input validation
if (codeword == null)
{
throw new NullReferenceException("Input MBE codeword is null!");
}
char[] bits = null;
// Set up based on mode
if (mode == MBE_MODE.DMR_AMBE)
{
if (codeword.Length != AMBE_CODEWORD_SAMPLES)
{
throw new ArgumentOutOfRangeException($"AMBE codeword length is != {AMBE_CODEWORD_SAMPLES}");
}
bits = new char[AMBE_CODEWORD_BITS];
}
else if (mode == MBE_MODE.IMBE_88BIT)
{
if (codeword.Length != IMBE_CODEWORD_SAMPLES)
{
throw new ArgumentOutOfRangeException($"IMBE codeword length is != {IMBE_CODEWORD_SAMPLES}");
}
bits = new char[IMBE_CODEWORD_BITS];
}
if (bits == null)
{
throw new NullReferenceException("Failed to initialize decoder");
}
// Decode
int errs = decoder.decodeBits(codeword, bits);
// Copy
if (mode == MBE_MODE.DMR_AMBE)
{
// Copy bits
mbeBits = new byte[AMBE_CODEWORD_BITS];
Array.Copy(bits, mbeBits, AMBE_CODEWORD_BITS);
}
else if (mode == MBE_MODE.IMBE_88BIT)
{
// Copy bits
mbeBits = new byte[IMBE_CODEWORD_BITS];
Array.Copy(bits, mbeBits, IMBE_CODEWORD_BITS);
}
return errs;
}
public void Encode([In] byte[] mbeBits, [Out] byte[] codeword)
{
if (mbeBits == null)
{
throw new NullReferenceException("Input MBE bit array is null!");
}
char[] bits = null;
// Set up based on mode
if (mode == MBE_MODE.DMR_AMBE)
{
if (mbeBits.Length != AMBE_CODEWORD_BITS)
{
throw new ArgumentOutOfRangeException($"AMBE codeword bit length is != {AMBE_CODEWORD_BITS}");
}
bits = new char[AMBE_CODEWORD_BITS];
Array.Copy(mbeBits, bits, AMBE_CODEWORD_BITS);
}
else if (mode == MBE_MODE.IMBE_88BIT)
{
if (mbeBits.Length != IMBE_CODEWORD_BITS)
{
throw new ArgumentOutOfRangeException($"IMBE codeword bit length is != {AMBE_CODEWORD_BITS}");
}
bits = new char[IMBE_CODEWORD_BITS];
Array.Copy(mbeBits, bits, IMBE_CODEWORD_BITS);
}
if (bits == null)
{
throw new ArgumentException("Bit array did not get set up properly!");
}
// Encode samples
if (mode == MBE_MODE.DMR_AMBE)
{
// Create output array
byte[] codewords = new byte[AMBE_CODEWORD_SAMPLES];
// Encode
encoder.encodeBits(bits, codewords);
// Copy
codeword = new byte[AMBE_CODEWORD_SAMPLES];
Array.Copy(codewords, codeword, IMBE_CODEWORD_SAMPLES);
}
else if (mode == MBE_MODE.IMBE_88BIT)
{
// Create output array
byte[] codewords = new byte[IMBE_CODEWORD_SAMPLES];
// Encode
encoder.encodeBits(bits, codewords);
// Copy
codeword = new byte[IMBE_CODEWORD_SAMPLES];
Array.Copy(codewords, codeword, IMBE_CODEWORD_SAMPLES);
}
}
}
}