How to make an audio tone control

For the final recipe of this chapter, we'll make a digital tone control that emulates analogue circuits found on portable radios, and so on. Simple analogue tone circuits take the form of an active filter that uses a potentiometer to affect the filter transfer function (that is, emphasizing low/high frequencies—bass/treble—in the audio signal.

Although this recipe illustrates our filter operating in real time, it isn't the most efficient way of filtering digital audio. The audio codec includes its own DSP processing block, and this can be programmed to produce similar results more efficiently. We'll refer to this recipe as codecDemo_c7v3.

Getting ready

The high- and low-pass FIR filter coefficients that we need for this recipe are found using MATLAB. We've chosen the pass and stop bands that are shown in the following screenshot:

Getting ready

How to do it…

  1. Clone codecDemo_c7v2 from the Designing a low-pass digital filter recipe and name the new folder codecDemo_c7v3.
  2. Use the runtime management tool to add board support for the A/D converter. Add this statement to initialize the A/D converter:
    ADC_Initialize_and_Set_IRQ();
  3. Add #include "Custom_ADC.h".
  4. Include the Custom_ADC.c file in the project and copy the Custom_ADC.h file into the project folder. We developed these in adcISR_c5v0 from the Setting up the ADC recipe in Chapter 5, Data Conversion.
  5. Add high-pass filter coefficients to the filter() function, as follows:
      static const float hpfiltCoef[] = 
    { 0.0511, 0.0540, 0.0524, 0.0533, 0.0528, 0.0517,
          0.0493, 0.0450, 0.0363, 0.0   , 0.1000, 0.0637,
          0.0550, 0.0507, 0.0483, 0.0472, 0.0467, 0.0476,  
          0.0460, 0.0489, 0.0 };                                      
  6. Modify the filter() function so that the output is formed by a weighted sum of low-pass and high-pass samples:
    for (coefIdx = 0; coefIdx<nTaps; coefIdx++) {
        lpVal += lpfiltCoef[coefIdx] *
                         smplBuff[(newIdx+coefIdx)%nTaps];
        hpVal += hpfiltCoef[coefIdx] *
                         smplBuff[(newIdx+coefIdx)%nTaps];
      }     
      outVal = (int16_t) ( (lpVal*sFactor +
                  hpVal*((float)1.0-sFactor)) * int16max);
  7. Add an ISR to service interrupts from the ADC, as follows:
    void ADC_IRQHandler (void) {
       
      ADC3->SR &= ~2;       /* Clear EOC interrupt flag */
      adcValue = (ADC3->DR)>> 4; /* Get converted value */
      ADC3->CR2 |= (1 << 30);  /* Start next conversion */
             
    } 
  8. Change the main() super loop so that we compute a global scale factor when we're not filtering the signal, that is, as follows:
    if (flag) {
      rightSmpl = i%2; 
      if (!rightSmpl)               /* run filter */
        sample = filter(data[i>>1]);
      else                  /* update scalefactor */
        sFactor = ( (float) adcValue ) / c;
            
      i++;
      i %= (sz<<1);                   /* MOD 2*sz */
      flag = false;
    }
  9. Add the following global variables:
    int32_t adcValue;                           
    float sFactor = 0.0;
    const float c = 255.0;                               
  10. Build, download, and run the program.

How it works…

The output sample is a weighed sum of the low-pass and unfiltered signal. These weights depend on the ADC value that, in turn, reflects the position of the potentiometer thumbwheel. The computation of the scale factor (0.0 ≤ sFactor ≤ 1.0) involves division, and as this is more time-consuming than the multiply accumulate operation, we choose to do this when we're not running the filter.

There's more...

To implement convolution requires the multiplication and addition of real numbers. These operations are performed by the Floating Point Unit (FPU) of the Cortex-M4. Real numbers are represented using a floating-point binary format. Early computers used many different (manufacturer-specific) formats to represent real numbers, but nowadays formats are standardized. The IEEE 754-2008 standard defines two formats known as IEEE double- and single-precision. Our programs use the single-precision (32-bit) format by declaring variables of the float type. Numbers encoded using the double-precision format are declared using the double (64-bit) type. It is important to understand that the representations of floating-point numbers approximate the real values that they represent and the rounding errors introduced can be particularly problematic for DSP applications.

Early 16-bit microprocessors, such as Intel 8086, were unable to carry out arithmetic operations on floating point numbers without using a floating point library, and users who didn't purchase the additional 8087 coprocessor were faced with quite poor performance. However, in the last decade, integrated hardware FPUs have become more common. Convolutions, at the heart of DSP applications, make repeated use of Multiply-Accumulate (MAC) operations, and processors aimed at DSP applications, such as the Cortex-M4, include specific instructions that allow these to be executed very efficiently.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.218.45