Skip to content

Commit dc687f1

Browse files
committed
added u-law, updated docs, added tests
1 parent c03974d commit dc687f1

File tree

9 files changed

+388
-159
lines changed

9 files changed

+388
-159
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131
*.out
3232
*.app
3333

34-
compandit
34+
compandit
35+
companders_fulltest
36+
.aider*

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2011-2016, M. A. Chatterjee <deftio at deftio dot com>
1+
Copyright (c) 2001-2024, M. A. Chatterjee <deftio at deftio dot com>
22
All rights reserved.
33

44
Redistribution and use in source and binary forms, with or without

README.md

Lines changed: 67 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
# [Companders (a fixed point audio compression library)](https://deftio.github.io/companders/)
44

5-
(c) 2001-2023 M. A. Chatterjee
5+
(c) 2001-2024 M. A. Chatterjee
66

77
This document is a brief overview of this simple audio compression library for microcontrollers using A-Law (a type of compander). This uses fixed-radix (integer only) math with a small introductory disucssion and use of associated DC-offset correction with an IIR fixed-radix filter.
88

99
The code in this repo is suitable for small microcontrollers such as 8 bit and 16 bit families (arduino, 68hc, 8051 ) as well as on 32 bit familes such as ARM, RISC-V, etc.
1010

11-
As this is a fixed point library (a type of integer math), floating point is not used or even emulated. For more background on using fixed point (or fixed radix) math see this code library and documentation : [FR_Math](https://github.com/deftio/fr_math)
11+
As this is a fixed point library (a type of integer math), floating point is not used or even emulated. For more background on using fixed point (or fixed radix) math see this code library and documentation : [FR_Math](https://github.com/deftio/fr_math)
1212

13-
## Welcome..
13+
## Welcome
1414

15-
The accompanying companders.c contains a small set of functions written in C using only integer math for companding operations. I developed this several years ago for use in several embedded interized math projects and this small, slightly cleaned up version is made available for the public here. It worked very well on M*CORE, 80x86 and ARM processors for small embedded systems.
15+
The accompanying companders.c contains a small set of functions written in C using only integer math for companding operations. I developed this several years ago for use in several embedded interized math projects and this small, slightly cleaned up version is made available for the public here. It worked very well on M*CORE, 80x86 and ARM processors for small embedded systems.
1616

1717
Usage:
1818

@@ -25,15 +25,13 @@ int myCompandedValue = DIO_LinearToALaw(123); // convert the integer to is A-Law
2525
int unCompandedValue = DIO_ALawToLinear(myCompandedValue); // convert back to linear range. (with appropriate loss)
2626
```
2727

28-
29-
30-
## About companding...
28+
## About Companding
3129

3230
Companding is a special type of lossy compression in which linear format samples are converted to logarithmic representation in order to preserve dynamic range of the original linear sample at the expense of uniform precision. Companding is an old method and is free of patents (all have expired).
3331

3432
Theory of how companding works:
3533
Suppose that we have a signed 16 bit linear sample in 2's complement math. The range of this sample can vary from -32768 to + 32767 in integer steps. Now lets put the constraint that we only have 8 bits with which to represent the data not the full 16 bits of the original linear number. A simple way to preserve high order information would be to linearly truncate off the lower 8 bits giving us a signed number from -128 to +127. We could make a mental note to keep track of the fact that we lost the lower 8 bits and so when use this representation we multiply by 8 bits (or 256) to preserve the input range.
36-
so:
34+
so:
3735

3836
```C
3937
-128 * 256 = -32768
@@ -49,32 +47,36 @@ so:
4947
```
5048

5149
Notice that the steps between these linearly rounded off samples are quite large. This truncated 8 bit representation would be very good at representing a linear quantity system such as linear displacement transducer which moves through its whole range as part of normal operation (like a door). but terrible at logarithmic phenomonen such as audio. Audio information tends to be grouped around zero with occaisonaly peaks of loudness. So with linear quantization soft audio would be lost due to the large quanitization steps at low volumes. To address this companders were developed for use in landline telephony for compressing audio data logarithmically instead of linearly. Hence we use more bits for audio samples near zero and less with audio samples near the integer max extremes.
52-
50+
5351
A-Law and it cousin mu-Law are companders. Rather than represent samples in linear steps the more bits are allocated to samples near zero while larger magnitude (positive or negative) samples are represented with proportionately larger interval sizes.
5452

5553
Differential encoding (taking the difference of neighboring samples) can greatly help with compressing data to where we "have more bits" as well, provided samples rates are fast enough. Telephony typically operated with 8 bit non-differential (companded) samples at 8KHz.
56-
54+
5755
In A-Law the following table (in bits) shows how a 13 bit signed linear integer is companded in to A-Law:
5856
(source is Sun microsystems, a similar table is on wikipedia or the G.711 specification).
5957

58+
| Linear Input Code | Compressed Code |
59+
|------------------------ | --------------- |
60+
| 0000000wxyza | 000wxyz |
61+
| 0000001wxyza | 001wxyz |
62+
| 000001wxyzab | 010wxyz |
63+
| 00001wxyzabc | 011wxyz |
64+
| 0001wxyzabcd | 100wxyz |
65+
| 001wxyzabcde | 101wxyz |
66+
| 01wxyzabcdef | 110wxyz |
67+
| 1wxyzabcdefg | 111wxyz |
68+
69+
### u-law (also mu-law)
70+
71+
Mu is similar compander to A-Law but was used in American & Japanese telephony instead of European Telephony.
72+
73+
A-Law is mainly used in European digital communications, while μ-Law is employed in North American and Japanese systems. Both algorithms aim to optimize the dynamic range of an audio signal by using logarithmic compression, but they differ in their compression characteristics and implementation. A-Law provides a slightly lower compression ratio, which offers better signal quality for signals with lower amplitude, whereas μ-Law provides higher compression, which can handle a wider range of input levels more efficiently but introduces more distortion for low-level signals. These standards were developed in the mid-20th century to improve the efficiency and quality of voice transmission over limited-bandwidth communication channels. A-Law was standardized by the International Telegraph and Telephone Consultative Committee (CCITT) and is detailed in the ITU-T G.711 recommendation, while μ-Law was standardized by the American National Standards Institute (ANSI).
6074

61-
| Linear Input Code | Compressed Code |
62-
|------------------------ | --------------- |
63-
| 0000000wxyza | 000wxyz |
64-
| 0000001wxyza | 001wxyz |
65-
| 000001wxyzab | 010wxyz |
66-
| 00001wxyzabc | 011wxyz |
67-
| 0001wxyzabcd | 100wxyz |
68-
| 001wxyzabcde | 101wxyz |
69-
| 01wxyzabcdef | 110wxyz |
70-
| 1wxyzabcdefg | 111wxyz |
71-
72-
7375
## About this library:
74-
76+
7577
This free library supports A-Law and IIR averages. A-Law is used for the companding while the IIR averagers can be used for for embedded ADCs where the zero point is set loosely. Since the companders are sensitive, and allocate more bits, to values near zero its important to define a good zero. For example a microcontroller has a pin with an ADC which is fed digitized audio signal. The smallest value for the microtroncoller is 0V = 0x00 and the 3.3V = 0x3ff for 10 effective bits of resolution. A resisitive analog divider is used to center the ADC near the half-input range or about 1.6V while the audio is capacitively coupled as shown here:
7678

77-
```
79+
```text
7880
7981
+3.3V
8082
|
@@ -88,15 +90,14 @@ uC_ADC_Pin<-----------C---- audio_input_source
8890
8991
```
9092

91-
9293
However cheap resistors have tolerances 5 to 10%, so the setpoint voltage could easily be anywhere from 1.4 to 1.8V. To address this software should read the ADC before audio is coming in and determine the actual DC bias voltage set by the real world resistors. Then when audio is coming in this value is subtracted from the ADC input to give a signed number which is fed to the LinearToAlaw encoder.
93-
94+
9495
If it not easily possible to turn off the analog / audio source then the long term DC average can be inferred by using one of the IIR functions included here with a long window length (perhaps 2000 samples if sampling at 8 KHz or about 1/4 second). These IIR functions are cheap to run in realtime even with reasonably high sample rates as they take little memory and are simple integer-math IIRs.
95-
96+
9697
## About the Fixed Radix (FR) Math IIR averages
97-
98+
9899
The (Infinite Impulse Reponse) IIR functions included here use fixed radix math to represent fractional parts of an integer. By providing a settable radix (amount of bits devoted to fractional resolution), the programmer can make tradeoffs between resolution and dynamic range. The larger the radix specified the more bits that will be used in the fractional portion of the representation, which for smaller window sizes may not by necessary. There are more comprehensive ways to deal with fractional representation in binary systems (floating point, bigNum / arbitrary length registers, separation of numerator/denomators etc) but these incur much larger compute/code/memory overhead. The simple system used here avoids the need for testing for overflow/underflow which allows for low foot print code/cpu/memory bandwidth.
99-
100+
100101
To calculate how many bits of fractional part while preventing overflow use the following formulas:
101102

102103
```C
@@ -117,22 +118,19 @@ radix = 32-Nb(sample_resolution)-Nb(WindowLength)-2
117118

118119
with 9 bits in the radix fractional precision of 512 units per integer (e.g 1/512) is possible. The "2" in for formula comes from reserving bits for the sign bit and the additional operation in averager.
119120

120-
121121
The function DIO_s32 DIO_IIRavgFR() allows any integer length window size to be used, while the function DIO_IIRavgPower2FR() specifies window lengths in powers of two but all calculations are based on shift operations which can be significantly faster on lower end microprocessors.
122122

123-
124123
## Embedded Systems
125124

126125
Now back to an embedded microcontroller example. Let's say it has an ADC which maps the voltage on 1 pin from 0-3.3V in to 0-4095 counts (12 bits). We capacitively couple the input voltage and use the bias resistors to set mid point in the center of the range. Lets say the the our resistors set the bias at 1.55V. This equals our "zero point". Below this point is negative from the incoming capacitively coupled audio and above this is positive.
127126

128127
Our "guess" as to the bias = both resistors are the same = (3.3V/2) =1.65V = (1.65V)/(4095 counts/3.3V)= 2048 counts
129128

130-
Resistor actual set bias "zero point" = 1.55V = (1.55V) *(4095 counts/3.3V)) = 1923 counts
129+
Resistor actual set bias "zero point" = 1.55V = (1.55V) *(4095 counts/3.3V)) = 1923 counts
131130
We want this to be "zero" we use for the companded A/D process.
132131

133132
To do this we start our ADC to periodically sample for sometime before we start "recording" audio. We will feed the ADC values in to our IIR average to find the actual zero point. Note that even when we decide not to "record" we still can still run the A/D and IIR averager so its adapting to the real zero point.
134133

135-
136134
## C-like Pseudo code
137135

138136
```C
@@ -143,63 +141,61 @@ static volatile int gDoSomethingWithCompandedValue=0;
143141

144142
void processAudioSample() //interrupt handler -- runs at the sample rate
145143
{
146-
short adcValue;
147-
char aLawValue;
148-
149-
//read the raw uncorrected sample (has some DC offset bias)
150-
adcValue= inputPin.read_u16(); // read a 16 bit unsigned sample
151-
152-
// this updates the IIR average everytime the adc returns a sample
153-
gIIRavg = DIO_IIRavgPower2FR(gIIRavg,11,adcValue,8);
154-
155-
//now compute companded value with DC offset correction
156-
if (gDoSomethingWithCompandedValue)
157-
{
158-
aLawValue = DIO_LinearToALaw(adcValue-DIO_FR2I(gIIRavg)); // subtract off the offset and get a good A-Law value
159-
doSomethingWithALaw(aLawValue); // store it to a buffer, send it the cloud ;) etc
160-
}
144+
short adcValue;
145+
char aLawValue;
146+
147+
//read the raw uncorrected sample (has some DC offset bias)
148+
adcValue= inputPin.read_u16(); // read a 16 bit unsigned sample
149+
150+
// this updates the IIR average everytime the adc returns a sample
151+
gIIRavg = DIO_IIRavgPower2FR(gIIRavg,11,adcValue,8);
152+
153+
//now compute companded value with DC offset correction
154+
if (gDoSomethingWithCompandedValue)
155+
{
156+
aLawValue = DIO_LinearToALaw(adcValue-DIO_FR2I(gIIRavg)); // subtract off the offset and get a good A-Law value
157+
doSomethingWithALaw(aLawValue); // store it to a buffer, send it the cloud ;) etc
158+
}
161159
}
162160

163161
main()
164162
{
165-
gDoSomethingWithCompandedValue=0;
166-
inputPin.attachInterrupt(&processAudioSample,8000); //attach interrupt handler, 8000 Hz sample rate
167-
168-
// ... somepoint later in the code
169-
170-
inputPin.start(); // start the collecting audio. inside the interrupt handler we'll start estimating the DC bias
171-
172-
// ... some time later (when we actually want the audio) we set this flag
173-
gDoSomethingWithCompandedValue=1; //now the interrupt routine will call the doSomethingWithALaw() function
174-
175-
// ... some time later
176-
gDoSomethingWithCompandedValue=0; //we're no longer collecting ALaw companded date, but we're
177-
// still running the IIR averager
178-
179-
163+
gDoSomethingWithCompandedValue=0;
164+
inputPin.attachInterrupt(&processAudioSample,8000); //attach interrupt handler, 8000 Hz sample rate
165+
166+
// ... somepoint later in the code
167+
168+
inputPin.start(); // start the collecting audio. inside the interrupt handler we'll start estimating the DC bias
169+
170+
// ... some time later (when we actually want the audio) we set this flag
171+
gDoSomethingWithCompandedValue=1; //now the interrupt routine will call the doSomethingWithALaw() function
172+
173+
// ... some time later
174+
gDoSomethingWithCompandedValue=0; //we're no longer collecting ALaw companded date, but we're
175+
// still running the IIR averager
176+
180177
}
181178

182179
```
180+
183181
The accompanying compandit.c file is an example program demonstrating the convergence of the averager to a simulated DC offset value (output is in the testout.txt file).
184182

185-
### Some Closing comments..
183+
### Some Closing Comments
186184

187185
Finally, it can be in some systems that we can't turn off the audio input source it may be hard wired to some sensor or mic or perhaps the A/D center bias circuit (the 2 resistors) always is on when the audio is on. In this case running the IIR with a long filter length all the time can remove the bias even when the audio is running. For example in an 8KHz sampling system with an IIR length of 1024 is about 1/8 of a second or a cutoff freq well below 10Hz and costs almost nothing to run.
188-
189-
186+
190187
## Versions
191188

192-
* 1.0.4 23 Jan 2023 -- updated docs
189+
* 1.0.4 30 May 2024 -- add ulaw, updated docs, added full test
190+
* 1.0.4 23 Jan 2023 -- updated docs
193191
* 1.0.3 28 Jul 2019 -- updated docs, ver example
194-
* 1.0.2 15 Jul 2016 -- updated README.md to markdown format. updated license to be OSI compliant. no code changes. some minor doc updates. Thanks to John R Strohm giving me the nudge to update the docs here.
192+
* 1.0.2 15 Jul 2016 -- updated README.md to markdown format. updated license to be OSI compliant. no code changes. some minor doc updates. Thanks to John R Strohm giving me the nudge to update the docs here.
195193
* 1.0.1 3 Sep 2012 -- original release in github
196194

197-
198-
199195
## License
200196
See attached LICENSE.txt file (OSI approved BSD 2.0)
201197

202-
Copyright (c) 2001-2019, M. A. Chatterjee < deftio at deftio dot com >
198+
Copyright (c) 2001-2024, M. A. Chatterjee < deftio at deftio dot com >
203199
All rights reserved.
204200

205201
Redistribution and use in source and binary forms, with or without

0 commit comments

Comments
 (0)