diff --git a/src/buffer.rs b/src/buffer.rs
index 13db9eedd91d8c6ff8974cf0d2cf96f5d257c5b4..f2bc8eab4b8b206a66a4ba16f2f5fdd169add3f4 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -4,28 +4,28 @@ use core::ops::{Deref, DerefMut};
 pub struct BufferOverflow;
 
 #[derive(Debug)]
-pub struct Buffer<'a, const N: usize> {
-    data: &'a mut [u8; N],
+pub struct Buffer<'a, const N: usize, T = u8> {
+    data: &'a mut [T; N],
     i: usize,
 }
 
-impl<'a, const N: usize> Buffer<'a, N> {
+impl<'a, const N: usize, T: Copy> Buffer<'a, N, T> {
     /// Constructs a new `Buffer`.
     /// `data` is the backing array.
     /// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`)
-    pub fn new(data: &'a mut [u8; N], i: usize) -> Self {
+    pub fn new(data: &'a mut [T; N], i: usize) -> Self {
         assert!(i <= data.len());
 
         Self { data, i }
     }
 
-    pub fn new_full(data: &'a mut [u8; N]) -> Self {
+    pub fn new_full(data: &'a mut [T; N]) -> Self {
         let i = data.len();
 
         Self::new(data, i)
     }
 
-    pub fn new_empty(data: &'a mut [u8; N]) -> Self {
+    pub fn new_empty(data: &'a mut [T; N]) -> Self {
         Self::new(data, 0)
     }
 
@@ -33,7 +33,7 @@ impl<'a, const N: usize> Buffer<'a, N> {
         N - self.i
     }
 
-    pub fn try_push(&mut self, v: u8) -> Result<(), BufferOverflow> {
+    pub fn try_push(&mut self, v: T) -> Result<(), BufferOverflow> {
         if self.i == N {
             return Err(BufferOverflow);
         }
@@ -44,11 +44,11 @@ impl<'a, const N: usize> Buffer<'a, N> {
         Ok(())
     }
 
-    pub fn push(&mut self, v: u8) {
+    pub fn push(&mut self, v: T) {
         self.try_push(v).unwrap();
     }
 
-    pub fn try_extend_from_slice(&mut self, other: &[u8]) -> Result<(), BufferOverflow> {
+    pub fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), BufferOverflow> {
         if self.remaining_capacity() < other.len() {
             return Err(BufferOverflow);
         }
@@ -59,11 +59,11 @@ impl<'a, const N: usize> Buffer<'a, N> {
         Ok(())
     }
 
-    pub fn extend(&mut self, other: &[u8]) {
+    pub fn extend(&mut self, other: &[T]) {
         self.try_extend_from_slice(other).unwrap();
     }
 
-    pub fn pop(&mut self) -> Option<u8> {
+    pub fn pop(&mut self) -> Option<T> {
         if self.i == 0 {
             return None;
         }
@@ -92,7 +92,7 @@ impl<'a, const N: usize> Buffer<'a, N> {
         self.i -= delta;
     }
 
-    pub fn clone_backing<'b>(&self, buf: &'b mut [u8; N]) -> Buffer<'b, N> {
+    pub fn clone_backing<'b>(&self, buf: &'b mut [T; N]) -> Buffer<'b, N, T> {
         let mut out = Buffer::new_empty(buf);
 
         out.extend(self);
@@ -101,22 +101,22 @@ impl<'a, const N: usize> Buffer<'a, N> {
     }
 }
 
-impl<'a, const N: usize> From<&'a mut [u8; N]> for Buffer<'a, N> {
-    fn from(data: &'a mut [u8; N]) -> Self {
+impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> {
+    fn from(data: &'a mut [T; N]) -> Self {
         Self { data, i: 0 }
     }
 }
 
-impl<'a, const N: usize> Deref for Buffer<'a, N> {
-    type Target = [u8];
+impl<'a, const N: usize, T> Deref for Buffer<'a, N, T> {
+    type Target = [T];
 
     fn deref(&self) -> &Self::Target {
         &self.data[..self.i]
     }
 }
 
-impl<'a, const N: usize> DerefMut for Buffer<'a, N> {
-    fn deref_mut(&mut self) -> &mut [u8] {
+impl<'a, const N: usize, T> DerefMut for Buffer<'a, N, T> {
+    fn deref_mut(&mut self) -> &mut [T] {
         &mut self.data[..self.i]
     }
 }
diff --git a/src/interleaver.rs b/src/interleaver.rs
index 5efeaaf7a3fdded2468749e22dd88dfe01383ff0..3e5fd9724179027aa819b3f36546e376f94bd2e6 100644
--- a/src/interleaver.rs
+++ b/src/interleaver.rs
@@ -1,4 +1,5 @@
 use bitvec::prelude::*;
+use labrador_ldpc::decoder::DecodeFrom;
 
 use crate::{
     buffer::{Buffer, BufferOverflow},
@@ -60,8 +61,52 @@ pub(crate) fn uninterleave<const N: usize>(
     Ok(())
 }
 
+pub(crate) fn interleave_soft<const N: usize, T: DecodeFrom>(
+    data: &[T],
+    out: &mut Buffer<N, T>,
+) -> Result<(), EncodeError> {
+    for i in 0..32 {
+        for j in (0..data.len()).step_by(32) {
+            if i + j >= data.len() {
+                continue;
+            }
+
+            out.try_push(data[i + j])
+                .map_err(|_| EncodeError::CatsOverflow)?;
+        }
+    }
+
+    Ok(())
+}
+
+pub(crate) fn uninterleave_soft<const N: usize, T: DecodeFrom>(
+    data: &[T],
+    out: &mut Buffer<N, T>,
+) -> Result<(), BufferOverflow> {
+    for _ in 0..data.len() {
+        out.try_push(T::zero())?;
+    }
+
+    let mut out_i = 0;
+    for i in 0..32 {
+        for j in (0..data.len()).step_by(32) {
+            if i + j >= data.len() {
+                continue;
+            }
+
+            out[i + j] = data[out_i];
+
+            out_i += 1;
+        }
+    }
+
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
+    use crate::soft_bit::SoftBit;
+
     use super::*;
 
     #[test]
@@ -84,4 +129,71 @@ mod tests {
 
         assert_eq!(*orig, *uninterleaved);
     }
+
+    #[test]
+    fn soft_interleaver_works() {
+        let mut data = [
+            57i16, 90, -103, -127, 85, -86, -43, -84, -116, -110, -36, -126, -74, 42, -28, -16, 93,
+            -22, -4, -110, -84, 84, -100, -109, 74, -86, 73, -64, -117, -93, 118, -26, 9, 69, -100,
+            80, -4, 26, -85, -118, 40, 64, -97, -7, 84, 118, 84, 127, -96, -18, 77, 1, 27, -20,
+            -95, -105, -120, 14, 119, -117, -103, 67, 58, -4, -98, 12, -5, -59, 74, -112, -9, 17,
+            -17, 67, -116, 12, -59, -118, 6, 99, 56, -27, -64, -87, -36, -51, 47, 19, -97, -63, 4,
+            5, 23, -38, -14, 16, -52, 54, -27, -90,
+        ];
+        let orig = Buffer::new_full(&mut data);
+
+        let mut interleaved = [0; 101];
+        let mut interleaved = Buffer::new(&mut interleaved, 0);
+
+        // check that existing data isn't mangled
+        interleaved.push(-1);
+
+        interleave_soft(&orig, &mut interleaved).unwrap();
+
+        let expected = [
+            -1, 57, 9, -98, -52, 90, 69, 12, 54, -103, -100, -5, -27, -127, 80, -59, -90, 85, -4,
+            74, -86, 26, -112, -43, -85, -9, -84, -118, 17, -116, 40, -17, -110, 64, 67, -36, -97,
+            -116, -126, -7, 12, -74, 84, -59, 42, 118, -118, -28, 84, 6, -16, 127, 99, 93, -96, 56,
+            -22, -18, -27, -4, 77, -64, -110, 1, -87, -84, 27, -36, 84, -20, -51, -100, -95, 47,
+            -109, -105, 19, 74, -120, -97, -86, 14, -63, 73, 119, 4, -64, -117, 5, -117, -103, 23,
+            -93, 67, -38, 118, 58, -14, -26, -4, 16,
+        ];
+        assert_eq!(expected, interleaved[..]);
+
+        let mut uninterleaved = [0; 101];
+        let mut uninterleaved = Buffer::new(&mut uninterleaved, 0);
+        uninterleave_soft(&interleaved[1..], &mut uninterleaved).unwrap();
+
+        assert_eq!(*orig, *uninterleaved);
+    }
+
+    #[test]
+    fn hard_interleave_soft_uninterleave() {
+        let mut data = [0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77];
+        let orig = Buffer::new_full(&mut data);
+
+        let mut interleaved = [0; 10];
+        let mut interleaved = Buffer::new(&mut interleaved, 0);
+        interleaved.push(b'H');
+
+        interleave(&orig, &mut interleaved).unwrap();
+
+        let mut soft_interleaved = [0.0; 10 * 8];
+        for (i, b) in interleaved.iter().enumerate() {
+            for j in 0..8 {
+                soft_interleaved[8 * i + j] = f32::from_hard_bit(b & (1 << (7 - j)) > 0);
+            }
+        }
+
+        let mut uninterleaved = [0.0; 10 * 8];
+        let mut uninterleaved = Buffer::new(&mut uninterleaved, 0);
+        uninterleave_soft(&soft_interleaved[8..], &mut uninterleaved).unwrap();
+
+        assert_eq!(orig.len() * 8, uninterleaved.len());
+        for (i, b) in orig.iter().enumerate() {
+            for j in 0..8 {
+                assert_eq!(uninterleaved[8 * i + j].hard_bit(), *b & (1 << (7 - j)) > 0);
+            }
+        }
+    }
 }
diff --git a/src/ldpc.rs b/src/ldpc.rs
index a3cda75036ccfb1cadf6a4159707913c59198dc5..71e5cc7e2fe9af85fbdf8e6d88a301a41782ed4f 100644
--- a/src/ldpc.rs
+++ b/src/ldpc.rs
@@ -1,6 +1,6 @@
-use labrador_ldpc::LDPCCode;
+use labrador_ldpc::{decoder::DecodeFrom, LDPCCode};
 
-use crate::{buffer::Buffer, error::EncodeError};
+use crate::{buffer::Buffer, error::EncodeError, soft_bit::SoftBit};
 
 macro_rules! enc_chunk {
     ($d:ident, $i:ident, $t:ident, $n:literal) => {
@@ -36,6 +36,27 @@ macro_rules! dec_chunk {
     };
 }
 
+macro_rules! dec_chunk_soft {
+    ($d:ident, $p:ident, $w:ident, $w_u8:ident, $out:ident, $t:ident, $n:literal) => {
+        ::paste::paste! {
+            let code_data = &mut $d[..$n];
+            let code_parity = &$p.get_mut(..$n)?;
+
+            let mut input = [T::zero(); $n * 2];
+            input[..$n].copy_from_slice(code_data);
+            input[$n..].copy_from_slice(code_parity);
+            const CODE: LDPCCode = LDPCCode::[<$t>];
+
+			let mut out_tmp = [0; CODE.output_len()];
+            CODE.decode_ms(&input, &mut out_tmp, &mut $w[..CODE.decode_ms_working_len()], &mut $w_u8[..CODE.decode_ms_working_u8_len()], 16);
+			$out.try_extend_from_slice(&out_tmp[..$n/8]).ok()?;
+
+            $d = &mut $d[$n..];
+            $p = &mut $p[$n..];
+        }
+    };
+}
+
 // On failure this still modifies the data array!
 pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeError> {
     let mut i = 0;
@@ -153,8 +174,111 @@ pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> {
     Some(())
 }
 
+pub(crate) fn decode_soft<const N: usize, const M: usize, T: DecodeFrom>(
+    data_av: &mut Buffer<N, T>,
+    out: &mut Buffer<M>,
+) -> Option<()> {
+    if data_av.len() % 8 != 0 {
+        return None;
+    }
+
+    if data_av.len() < 16 {
+        return None;
+    }
+
+    let len: usize = len_from_soft(data_av[(data_av.len() - 16)..].try_into().unwrap()).into();
+
+    if len >= data_av.len() {
+        return None;
+    }
+
+    let (mut data, parity) = data_av.split_at_mut(len * 8);
+    let mut parity = parity.get_mut(..parity.len().checked_sub(16)?)?;
+
+    let mut working = [T::zero(); LDPCCode::TM8192.decode_ms_working_len()];
+    let mut working_u8 = [0; LDPCCode::TM8192.decode_ms_working_u8_len()];
+
+    loop {
+        match data.len() {
+            4096.. => {
+                dec_chunk_soft!(data, parity, working, working_u8, out, TM8192, 4096);
+            }
+
+            1024.. => {
+                dec_chunk_soft!(data, parity, working, working_u8, out, TM2048, 1024);
+            }
+
+            256.. => {
+                dec_chunk_soft!(data, parity, working, working_u8, out, TC512, 256);
+            }
+
+            128.. => {
+                dec_chunk_soft!(data, parity, working, working_u8, out, TC256, 128);
+            }
+
+            64.. => {
+                dec_chunk_soft!(data, parity, working, working_u8, out, TC128, 64);
+            }
+
+            0 => break,
+
+            _ => {
+                // Extra bits are padded with 0xAA
+                // We need to tell the soft decoder that these bits can't have flipped
+                // So we set 1 to -50 and 0 to +50.
+                // There is probably a better way to do this
+                let mut code_data = [
+                    1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+                    1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+                    1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+                ]
+                .map(|x| T::from_hard_bit(x > 0)); // TODO !!
+                code_data[..data.len()].copy_from_slice(data);
+                let code_parity = &parity.get_mut(..64)?;
+
+                let mut input = [T::zero(); 128];
+                input[..64].copy_from_slice(&code_data);
+                input[64..].copy_from_slice(code_parity);
+                let mut tmp_out = [0; LDPCCode::TC128.output_len()];
+                LDPCCode::TC128.decode_ms(
+                    &input,
+                    &mut tmp_out,
+                    &mut working[..LDPCCode::TC128.decode_ms_working_len()],
+                    &mut working_u8[..LDPCCode::TC128.decode_ms_working_u8_len()],
+                    16,
+                );
+                out.try_extend_from_slice(&tmp_out[..(data.len() / 8)])
+                    .ok()?;
+
+                data = &mut data[..0];
+                parity = &mut parity[..0];
+            }
+        }
+    }
+
+    Some(())
+}
+
+fn len_from_soft<T: DecodeFrom>(bits: &[T; 16]) -> u16 {
+    let mut upper = 0;
+    for b in &bits[0..8] {
+        upper <<= 1;
+        upper |= u8::from(b.hard_bit());
+    }
+
+    let mut lower = 0;
+    for b in &bits[8..] {
+        lower <<= 1;
+        lower |= u8::from(b.hard_bit());
+    }
+
+    u16::from_le_bytes([upper, lower])
+}
+
 #[cfg(test)]
 mod tests {
+    use bitvec::{order::Msb0, view::BitView};
+
     use super::*;
 
     #[test]
@@ -230,4 +354,30 @@ mod tests {
 
         assert_eq!(*orig, *data);
     }
+
+    #[test]
+    fn basic_encode_decode_soft() {
+        let mut buf = [0; 8191];
+        let mut buf2 = [0; 8191];
+        let mut data = Buffer::new_empty(&mut buf);
+        for _ in 0..50 {
+            data.extend(b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)");
+        }
+        let orig = data.clone_backing(&mut buf2);
+
+        encode(&mut data).unwrap();
+        assert_ne!(*orig, *data);
+
+        let mut soft = [0.0; 8191 * 8];
+        let mut soft = Buffer::new_empty(&mut soft);
+        for b in data.view_bits::<Msb0>() {
+            soft.push(f32::from_hard_bit(*b));
+        }
+
+        let mut out = [0; 8191];
+        let mut out = Buffer::new_empty(&mut out);
+        decode_soft(&mut soft, &mut out).unwrap();
+
+        assert_eq!(*orig, *out);
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 949884264dbbf964753d7be43ab73792fc86c3aa..e2939177b9799de4a9299ae6548214d5d3720482 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@ pub mod identity;
 pub mod interleaver;
 pub mod ldpc;
 pub mod packet;
+pub mod soft_bit;
 pub mod whisker;
 pub mod whitener;
 
diff --git a/src/packet.rs b/src/packet.rs
index 66f05704ed3ddad9d82bdcb95ab83f3d720d83bf..13f9db32c460e1c3f5074d6bd59364f40db0ff34 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -1,6 +1,7 @@
 use core::fmt::Debug;
 
 use crc::{Crc, CRC_16_IBM_SDLC};
+use labrador_ldpc::decoder::DecodeFrom;
 
 use crate::{
     buffer::{Buffer, BufferOverflow},
@@ -183,7 +184,7 @@ impl<'a, const N: usize> Packet<'a, N> {
 
     /// Decodes packet that was received over the air.
     /// Packet shouldn't have preamble, sync word, or data langth L.
-    /// Expects bytes in the `buf`
+    /// Expects bytes in `data`
     /// `buf` is used as the backing buffer for the Packet
     pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> {
         let mut buf = Buffer::new_empty(buf);
@@ -194,6 +195,22 @@ impl<'a, const N: usize> Packet<'a, N> {
         Self::semi_decode(buf)
     }
 
+    /// Expects soft bits in `data`. Bits should be LLR, with positive numbers more likely to be 0.
+    /// Returns `DecodeError::Overflow` if `M` is less than `data.len()`.
+    pub fn fully_decode_soft<const M: usize, T: DecodeFrom>(
+        data: &mut [T],
+        buf: &'a mut [u8; N],
+    ) -> Result<Self, DecodeError> {
+        let mut out = [T::zero(); M];
+        let mut out = Buffer::new_empty(&mut out);
+        interleaver::uninterleave_soft(data, &mut out).map_err(|_| DecodeError::Overflow)?;
+        let mut buf = Buffer::new_empty(buf);
+        ldpc::decode_soft(&mut out, &mut buf).ok_or(DecodeError::LdpcError)?;
+        whitener::whiten(&mut buf);
+
+        Self::semi_decode(buf)
+    }
+
     pub fn iter(&self) -> ValidatedWhiskerIter {
         ValidatedWhiskerIter::new(&self.buf)
     }
@@ -391,8 +408,9 @@ fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>>
 #[cfg(test)]
 mod tests {
     use arrayvec::ArrayString;
+    use bitvec::{order::Msb0, view::BitView};
 
-    use crate::whisker::NodeInfoBuilder;
+    use crate::{soft_bit::SoftBit, whisker::NodeInfoBuilder};
 
     use super::*;
 
@@ -519,6 +537,60 @@ mod tests {
         assert_eq!(comment, packet2.comment(&mut buf).unwrap());
     }
 
+    #[test]
+    fn full_e2e_soft_decode() {
+        let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.";
+
+        let mut buf = [0; 4096];
+        let mut packet = Packet::new(&mut buf);
+        packet
+            .add_identification(Identification {
+                icon: 123,
+                callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(),
+                ssid: 43,
+            })
+            .unwrap();
+
+        packet.add_comment(comment).unwrap();
+
+        let res = packet.add_identification(Identification {
+            icon: 456,
+            callsign: ArrayString::from("NOPE").unwrap(),
+            ssid: 0,
+        });
+        assert!(matches!(res, Err(EncodeError::DuplicateData)));
+
+        let mut buf2 = [0; 4096];
+        let mut fully = Buffer::new_empty(&mut buf2);
+        packet.fully_encode(&mut fully).unwrap();
+
+        fully[40] ^= 0x55;
+        fully[844] ^= 0x7B;
+
+        let mut soft = [0.0; 8191 * 8];
+        let mut soft = Buffer::new_empty(&mut soft);
+        for b in fully.view_bits::<Msb0>().iter() {
+            soft.push(f32::from_hard_bit(*b));
+        }
+        let soft = &mut soft[16..];
+
+        let mut buf3 = [0; 8191];
+        // exclude length
+        let packet2: Packet<8191> =
+            Packet::fully_decode_soft::<{ 8191 * 8 }, _>(soft, &mut buf3).unwrap();
+        assert_eq!(
+            Identification {
+                icon: 123,
+                callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(),
+                ssid: 43,
+            },
+            packet2.identification().unwrap()
+        );
+
+        let mut buf = [0; 1024];
+        assert_eq!(comment, packet2.comment(&mut buf).unwrap());
+    }
+
     #[test]
     fn node_info_e2e() {
         let mut buf = [0; 4096];
diff --git a/src/soft_bit.rs b/src/soft_bit.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a7ed66861c5159a57a6601dc3a692e8b306a8958
--- /dev/null
+++ b/src/soft_bit.rs
@@ -0,0 +1,45 @@
+use labrador_ldpc::decoder::DecodeFrom;
+
+// Less than zero = 1 bit
+// Greater than zero = 0 bit
+pub trait SoftBit {
+    fn from_hard_bit(bit: bool) -> Self;
+    fn hard_bit(&self) -> bool;
+}
+
+impl<T: DecodeFrom> SoftBit for T {
+    fn from_hard_bit(bit: bool) -> Self {
+        if bit {
+            -Self::one()
+        } else {
+            Self::one()
+        }
+    }
+
+    fn hard_bit(&self) -> bool {
+        self < &T::zero()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn e2e() {
+        assert!(f32::from_hard_bit(true).hard_bit());
+        assert!(!f32::from_hard_bit(false).hard_bit());
+
+        assert!(f64::from_hard_bit(true).hard_bit());
+        assert!(!f64::from_hard_bit(false).hard_bit());
+
+        assert!(i8::from_hard_bit(true).hard_bit());
+        assert!(!i8::from_hard_bit(false).hard_bit());
+
+        assert!(i16::from_hard_bit(true).hard_bit());
+        assert!(!i16::from_hard_bit(false).hard_bit());
+
+        assert!(i32::from_hard_bit(true).hard_bit());
+        assert!(!i32::from_hard_bit(false).hard_bit());
+    }
+}
diff --git a/src/whitener.rs b/src/whitener.rs
index 0c66dc54825bd0d2415ae3baaada447aab8a0b01..9744959ac51e4f93e0375281acd9ec0649c94155 100644
--- a/src/whitener.rs
+++ b/src/whitener.rs
@@ -1,5 +1,9 @@
+use labrador_ldpc::decoder::DecodeFrom;
+
+const START_STATE: u16 = 0xE9CF;
+
 pub(crate) fn whiten(data: &mut [u8]) {
-    let mut state = 0xE9CF;
+    let mut state = START_STATE;
 
     for d in data.iter_mut() {
         let b;
@@ -8,6 +12,20 @@ pub(crate) fn whiten(data: &mut [u8]) {
     }
 }
 
+// One bit per element
+pub(crate) fn whiten_soft<T: DecodeFrom>(data: &mut [T]) {
+    let mut state = START_STATE;
+
+    for d in data.iter_mut() {
+        let b = state & 1;
+        if b > 0 {
+            // flip the soft bit. In LLR speak, this just means flip the sign
+            *d = -*d;
+        }
+        state = lfsr(state);
+    }
+}
+
 // (byte, state)
 fn lfsr_byte(mut state: u16) -> (u8, u16) {
     let mut out = 0;
@@ -33,6 +51,7 @@ fn lfsr(mut state: u16) -> u16 {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::soft_bit::SoftBit;
 
     #[test]
     fn basic() {
@@ -48,6 +67,44 @@ mod tests {
         assert_eq!(orig, data);
     }
 
+    #[test]
+    fn basic_soft() {
+        let mut data = [-2.3, 4.7, 0.0, 2.7, 7.8, 45.2, -0.1, -0.82];
+        let orig = data;
+
+        whiten_soft(&mut data);
+        assert_eq!([2.3, -4.7, -0.0, -2.7, 7.8, 45.2, 0.1, 0.82], data);
+
+        whiten_soft(&mut data);
+        assert_eq!(orig, data);
+    }
+
+    #[test]
+    fn compare_soft_and_hard() {
+        let mut data = [0; 64];
+        data[0..57]
+            .clone_from_slice(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]);
+        let orig = data;
+
+        whiten(&mut data);
+
+        let mut soft = [0.0; 64 * 8];
+        for (i, b) in data.iter().enumerate() {
+            for j in 0..8 {
+                soft[8 * i + j] = f32::from_hard_bit(b & (1 << (7 - j)) > 0);
+            }
+        }
+
+        whiten_soft(&mut soft);
+
+        assert_eq!(orig.len() * 8, soft.len());
+        for (i, b) in orig.iter().enumerate() {
+            for j in 0..8 {
+                assert_eq!(soft[8 * i + j].hard_bit(), *b & (1 << (7 - j)) > 0);
+            }
+        }
+    }
+
     #[test]
     fn test_lfsr() {
         let start = 0xACE1;