From a051b83f3a39c903740fc2d51d5ee25a32d5e408 Mon Sep 17 00:00:00 2001 From: Henrik Date: Sun, 16 Aug 2026 18:20:53 +0200 Subject: [PATCH 1/2] Find the G.711 segment by counting leading zeros when encoding --- src/alaw.rs | 28 +++++++++++++--------------- src/ulaw.rs | 28 ++++++++++++++-------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/alaw.rs b/src/alaw.rs index f6b828d..f5998fd 100644 --- a/src/alaw.rs +++ b/src/alaw.rs @@ -46,7 +46,11 @@ pub fn decode_alaw(encoded: u8) -> i16 { } // encoding algorithm is based on "A-Law and mu-Law Companding Implementations Using the TMS320C54x, -// Application Note: SPRA163A", page 16: https://www.ti.com/lit/an/spra163a/spra163a.pdf +// Application Note: SPRA163A", pages 18 and 23: https://www.ti.com/lit/an/spra163a/spra163a.pdf +// the segment (chord) is the position of the highest set bit, so counting leading zeros gives it +// directly, without the linear to A-law table on page 16. the application note does the same with +// the EXP instruction, which "allows the extraction of the most significant bits without requiring +// a look-up table" (page 18), and determines the A-law chord that way on page 23 // see also https://en.wikipedia.org/wiki/G.711#A-law /// Encodes a linear 16-bit signed integer sample value to a 8-bit encoded G.711 A-law value. @@ -60,24 +64,18 @@ pub fn encode_alaw(linear: i16) -> u8 { }; #[allow(clippy::cast_sign_loss)] // sign loss is expected and handled after the cast to u16 let linear = (linear >> 3) as u16; - let inputval = if sign == 0x80 { + let inputval = u32::from(if sign == 0x80 { // make a positive value using 1s' complement (a tip from wikipedia) linear ^ 0xffff } else { linear - }; - let compressed_code_word: u16 = match inputval { - #[allow(clippy::identity_op)] - 0b000000000000..=0b000000011111 => 0b000_0000 | (inputval & 0b000000011110) >> 1, - 0b000000100000..=0b000000111111 => 0b001_0000 | (inputval & 0b000000011110) >> 1, - 0b000001000000..=0b000001111111 => 0b010_0000 | (inputval & 0b000000111100) >> 2, - 0b000010000000..=0b000011111111 => 0b011_0000 | (inputval & 0b000001111000) >> 3, - 0b000100000000..=0b000111111111 => 0b100_0000 | (inputval & 0b000011110000) >> 4, - 0b001000000000..=0b001111111111 => 0b101_0000 | (inputval & 0b000111100000) >> 5, - 0b010000000000..=0b011111111111 => 0b110_0000 | (inputval & 0b001111000000) >> 6, - 0b100000000000..=0b111111111111 => 0b111_0000 | (inputval & 0b011110000000) >> 7, - 4096.. => 0b111_1111 - }; + }); + // the number of bits inputval has beyond the first segment, which ends at 0b000000011111. + // inputval never needs more than 12 bits, so the segment is always 0..=7 + let segment = (u32::BITS - inputval.leading_zeros()).saturating_sub(5); + // segments 0 and 1 have the same step size, so they use the same shift + let shift = if segment < 2 { 1 } else { segment }; + let compressed_code_word: u32 = (segment << 4) | ((inputval >> shift) & 0b1111); #[allow(clippy::cast_possible_truncation)] // compressed_code_word is always less than 255 let result = (sign | compressed_code_word as u8) ^ 0xd5; result diff --git a/src/ulaw.rs b/src/ulaw.rs index 7cfc8ff..134e464 100644 --- a/src/ulaw.rs +++ b/src/ulaw.rs @@ -46,7 +46,11 @@ pub fn decode_ulaw(encoded: u8) -> i16 { } // encoding algorithm is based on "A-Law and mu-Law Companding Implementations Using the TMS320C54x, -// Application Note: SPRA163A", page 13: https://www.ti.com/lit/an/spra163a/spra163a.pdf +// Application Note: SPRA163A", pages 18 and 20: https://www.ti.com/lit/an/spra163a/spra163a.pdf +// the segment (chord) is the position of the highest set bit, so counting leading zeros gives it +// directly, without the binary encoding table on page 13. the application note does the same with +// the EXP instruction, which "allows the extraction of the most significant bits without requiring +// a look-up table" (page 18), giving mchd = (0x19 - T|EXP) << 4 on page 20 // see also https://en.wikipedia.org/wiki/G.711#μ-law /// Encodes a linear 16-bit signed integer sample value to a 8-bit encoded G.711 μ-law value. @@ -60,24 +64,20 @@ pub fn encode_ulaw(linear: i16) -> u8 { }; #[allow(clippy::cast_sign_loss)] // sign loss is expected and handled after the cast to u16 let linear = (linear >> 2) as u16; - let absval = if sign == 0x80 { + let absval = u32::from(if sign == 0x80 { // make a positive value using 1s' complement (a tip from wikipedia) linear ^ 0xffff } else { linear - }; + }); let inputval = absval + 33; - let compressed_code_word = match inputval { - #[allow(clippy::identity_op)] - 0b0000000000000..=0b0000000111111 => 0b000_0000 | (inputval & 0b0000000011110) >> 1, - 0b0000001000000..=0b0000001111111 => 0b001_0000 | (inputval & 0b0000000111100) >> 2, - 0b0000010000000..=0b0000011111111 => 0b010_0000 | (inputval & 0b0000001111000) >> 3, - 0b0000100000000..=0b0000111111111 => 0b011_0000 | (inputval & 0b0000011110000) >> 4, - 0b0001000000000..=0b0001111111111 => 0b100_0000 | (inputval & 0b0000111100000) >> 5, - 0b0010000000000..=0b0011111111111 => 0b101_0000 | (inputval & 0b0001111000000) >> 6, - 0b0100000000000..=0b0111111111111 => 0b110_0000 | (inputval & 0b0011110000000) >> 7, - 0b1000000000000..=0b1111111111111 => 0b111_0000 | (inputval & 0b0111100000000) >> 8, - 8192.. => 0b111_1111 + // the number of bits inputval has beyond the first segment, which ends at 0b0000000111111 + let segment = (u32::BITS - inputval.leading_zeros()).saturating_sub(6); + let compressed_code_word: u32 = if segment >= 8 { + // inputval is past the last segment + 0b111_1111 + } else { + (segment << 4) | ((inputval >> (segment + 1)) & 0b1111) }; #[allow(clippy::cast_possible_truncation)] // compressed_code_word is always less than 255 let result = (sign | compressed_code_word as u8) ^ 0xff; From 00c4edf94fe685598854d581c0787beefe0b6efc Mon Sep 17 00:00:00 2001 From: Henrik Date: Mon, 17 Aug 2026 10:17:52 +0200 Subject: [PATCH 2/2] Avoid a zero check and a clamp in the A-law segment search --- src/alaw.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/alaw.rs b/src/alaw.rs index f5998fd..4b5f4d0 100644 --- a/src/alaw.rs +++ b/src/alaw.rs @@ -71,8 +71,10 @@ pub fn encode_alaw(linear: i16) -> u8 { linear }); // the number of bits inputval has beyond the first segment, which ends at 0b000000011111. + // the or never moves the highest set bit, but it makes inputval nonzero and at least 5 bits + // long, so that the bit count needs no zero check and the subtraction cannot underflow. // inputval never needs more than 12 bits, so the segment is always 0..=7 - let segment = (u32::BITS - inputval.leading_zeros()).saturating_sub(5); + let segment = (u32::BITS - (inputval | 0b11111).leading_zeros()) - 5; // segments 0 and 1 have the same step size, so they use the same shift let shift = if segment < 2 { 1 } else { segment }; let compressed_code_word: u32 = (segment << 4) | ((inputval >> shift) & 0b1111);