Hide navigation sidebar
Hide table of contents sidebar
Back to top
Toggle table of contents sidebar

# C++ API Reference

## MicArray

template<unsigned MIC_COUNT, class TDecimator, class TPdmRx, class TSampleFilter, class TOutputHandler>
class MicArray

  Represents the microphone array component of an application.

  Like many classes in this library, FrameOutputHandler uses the Curiously Recurring Template Pattern.

  Template Parameters:

    - MIC_COUNT –

      The number of microphones to be captured by the `MicArray`’s `PdmRx` component. For example, if using a 4-bit port to capture 6 microphone channels in a DDR configuration (because there are no 3 or 6 pin ports) `MIC_COUNT` should be `8`, because that’s how many must be captured, even if two of them are stripped out before passing audio frames to subsequent application stages.

    - TDecimator – Type for the decimator. See Decimator.

    - TPdmRx – Type for the PDM rx service used. See PdmRx.

    - TSampleFilter – Type for the output filter used. See SampleFilter.

    - TOutputHandler – Type for the output handler used. See OutputHandler.

  Public Functions

  inline MicArray()

    Construct a `MicArray`.

    This constructor uses the default constructor for each of its components, PdmRx, Decimator, SampleFilter, and OutputHandler.

  inline MicArray(TPdmRx pdm_rx, TSampleFilter sample_filter, TOutputHandler output_handler)

    Construct a `MicArray`.

    This constructor uses the default constructor for its Decimator component.

    The remaining components are initialized with the supplied objects.

    Parameters:

      - pdm_rx – The PDM rx object.

      - sample_filter – The SampleFilter object.

      - output_handler – The OutputHandler object.

  inline MicArray(TPdmRx pdm_rx, TOutputHandler output_handler)

    Construct a `MicArray`

    This constructor uses the default constructor for its Decimator and SampleFilter components.

    The remaining components are initialized with the supplied objects.

    Parameters:

      - pdm_rx – The PDM rx object.

      - output_handler – The OutputHandler object.

  void ThreadEntry()

    Entry point for the decimation thread.

    This function does not return. It loops indefinitely, collecting blocks of PDM data from PdmRx (which must have already been started), uses Decimator to filter and decimate the sample stream to the output sample rate, applies any post-processing with SampleFilter, and then delivers the stream of output samples through OutputHandler.

  Public Members

  TPdmRx PdmRx

    The PDM rx service.

    The template parameter `TPdmRx` is the concrete class implementing the microphone array’s PDM rx service, which is responsible for collecting PDM samples from a port and delivering them to the decimation thread.

    `TPdmRx` is only required to implement one function, `GetPdmBlock()`:

        uint32_t* GetPdmBlock();

    `GetPdmBlock()` returns a pointer to a block of PDM data, formatted as expected by the decimator. `GetPdmBlock()` is called from the decimator thread and is expected to block until a new full block of PDM data is available to be decimated.

    For example, StandardPdmRxService::GetPdmBlock() waits to receive a pointer to a block of PDM data from a streaming channel. The pointer is sent from the PdmRx interrupt (or thread) when the block has been completed. This is used for capturing PDM data from a port.

  TDecimator Decimator

    The Decimator.

    The template parameter `TDecimator` is the concrete class implementing the microphone array’s decimation procedure. `TDecimator` is only required to implement one function, `ProcessBlock()`:

        void ProcessBlock(
            int32_t sample_out[MIC_COUNT],
            uint32_t pdm_block[BLOCK_SIZE]);

    `ProcessBlock()` takes a block of PDM samples via its `pdm_block` parameter, applies the appropriate decimation logic, and outputs a single (multi-channel) sample sample via its `sample_out` parameter. The size and formatting of the PDM block expected by the decimator depends on its particular implementation.

    A concrete class based on the mic_array::TwoStageDecimator class template is used in the prefab::BasicMicArray prefab.

  TSampleFilter SampleFilter

    The output filter.

    The template parameter `TSampleFilter` is the concrete class implementing the microphone array’s sample filter component. This component can be used to apply additional non-decimating, non-interpolating filtering of samples. `TSampleFilter()` is only required to implement one function, `Filter()`:

        void Filter(int32_t sample[MIC_COUNT]);

    `Filter()` takes a single (multi-channel) sample from the decimator component’s output and may update the sample in-place.

    For example a sample filter based on the DcoeSampleFilter class template applies a simple first-order IIR filter to the output of the decimator, in order to eliminate the DC component of the audio signals.

    If no additional filtering is required, the NopSampleFilter class template can be used for `TSampleFilter`, which leaves the sample unmodified. In this case, it is expected that the call to NopSampleFilter::Filter() will ultimately get completely eliminated at build time. That way no addition run-time compute or memory costs need be introduced for the additional flexibility.

    Even though `TDecimator` and `TSampleFilter` both (possibly) apply filtering, they are separate components of the `MicArray` because they are conceptually independent.

    A concrete class based on either the DcoeSampleFilter class template or the NopSampleFilter class template is used in the prefab::BasicMicArray prefab, depending on the `USE_DCOE` parameter of that class template.

  TOutputHandler OutputHandler

    The output handler.

    The template parameter `TOutputHandler` is the concrete class implementing the microphone array’s output handler component. After the PDM input stream has been decimated to the appropriate output sample rate, and after any post-processing of that output stream by the sample filter, the output samples must be delivered to another thread for any additional processing. It is the responsibility of this component to package and deliver audio samples to subsequent processing stages.

    `TOutputHandler` is only required to implement one function, `OutputSample()`:

        void OutputSample(int32_t sample[MIC_COUNT]);

    `OutputSample()` is called exactly once for each mic array output sample. `OutputSample()` may block if necessary until the subsequent processing stage ready to receive new data. However, the decimator thread (in which `OutputSample()` is called) as a whole has a real-time constraint - it must be ready to pull the next block of PDM data while it is available.

    A concrete class based on the FrameOutputHandler class template is used in the prefab::BasicMicArray prefab.

  Public Static Attributes

  static constexpr unsigned MicCount = MIC_COUNT

    Number of microphone channels.

## BasicMicArray

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN = MIC_COUNT>
class BasicMicArray : public mic_array::MicArray<MIC_COUNT, TwoStageDecimator<MIC_COUNT, STAGE2_DEC_FACTOR, STAGE2_TAP_COUNT>, StandardPdmRxService<MIC_COUNT, MIC_COUNT, STAGE2_DEC_FACTOR>, std::conditional<USE_DCOE, DcoeSampleFilter<MIC_COUNT>, NopSampleFilter<MIC_COUNT>>::type, FrameOutputHandler<MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitter>>

  Class template for a typical bare-metal mic array unit.

  This prefab is likely the right starting point for most applications.

  With this prefab, the decimator will consume one device core, and the PDM rx service can be run either as an interrupt, or as an additional thread. Normally running as an interrupt is recommended.

  For the first and second stage decimation filters, this prefab uses the coefficients provided with this library. The first stage uses a decimation factor of 32, and the second stage is configured to use a decimation factor of 6.

  To get 16 kHz audio output from the `BasicMicArray` prefab, then, the PDM clock must be configured to `3.072 MHz` (`3.072 MHz / (32 * 6) = 16 kHz`).

  Sub-Components

    Being derived from mic_array::MicArray, an instance of `BasicMicArray` has 4 sub-components responsible for different portions of the work being done. These sub-components are `PdmRx`, `Decimator`, `SampleFilter` and `OutputHandler`. See the documentation for `MicArray` for more details about these.

  Template Parameters Details

    The template parameter `MIC_COUNT` is the number of microphone channels to be processed and output.

    The template parameter `FRAME_SIZE` is the number of samples in each output frame produced by the mic array. Frame data is communicated using the API found in `mic_array/frame_transfer.h`.

    Typically `ma_frame_rx()` will be the right function to use in a receiving thread to retrieve audio frames. `ma_frame_rx()` receives audio frames with shape `(MIC_COUNT,FRAME_SIZE)`, meaning that all samples corresponding to a given channel will end up in a contiguous block of memory. Instead of `ma_frame_rx()`, `ma_frame_rx_transpose()` can be used to swap the dimensions, resulting in the shape `(FRAME_SIZE, MIC_COUNT)`.

    Note that calls to `ma_frame_rx()` or `ma_frame_rx_transpose()` will block until a frame becomes available on the specified chanend.

    If the receiving thread is not waiting to retrieve the audio frame from the mic array when it becomes available, the pipeline may back up and cause samples to be dropped. It is the responsibility of the application developer to ensure this does not happen.

    The boolean template parameter `USE_DCOE` indicates whether the DC offset elimination filter should be applied to the output of the second stage decimator. DC offset elimination is an IIR filter intended to ensure audio samples on each channel tend towards zero-mean.

    For more information about DC offset elimination, see Sample Filters

    .

    If `USE_DCOE` is `false`, no further filtering of the second stage decimator’s output will occur.

    The template parameter `MICS_IN` indicates the number of microphone channels to be captured by the `PdmRx` component of the mic array unit. This will often be the same as `MIC_COUNT`, but in some applications, `MIC_COUNT` microphones must be physically connected to an XCore port which is not `MIC_COUNT` (SDR) or `MIC_COUNT/2` (DDR) bits wide.

    In these cases, capturing the additional channels (likely not even physically connected to PDM microphones) is unavoidable, but further processing of the additional (junk) channels can be avoided by using `MIC_COUNT < MICS_IN`. The mapping which tells the mic array unit how to derive output channels from input channels can be configured during initialization by calling `StandardPdmRxService::MapChannels()` on the `PdmRx` sub-component of the `BasicMicarray`.

    If the application uses an SDR microphone configuration (i.e. 1 microphone per port pin), then `MICS_IN` must be the same as the port width. If the application is running in a DDR microphone configuration, `MICS_IN` must be twice the port width. `MICS_IN` defaults to `MIC_COUNT`.

  Allocation

    Before a mic array unit can be started or initialized, it must be allocated.

    Instances of `BasicMicArray` are self-contained with respect to memory, needing no external buffers to be supplied by the application. Allocating an instance is most easily accomplished by simply declaring the mic array unit. An example follows.

        #include "mic_array/cpp/Prefab.hpp"
        ...
        using AppMicArray = mic_array::prefab::BasicMicArray<MICS,SAMPS,DCOE>;
        AppMicArray mics;

    Here, `mics` is an allocated mic array unit. The example (and all that follow) assumes the macros used for template parameters are defined elsewhere.

  Initialization

    Before a mic array unit can be started, it must be initialized.

    `BasicMicArray` reads PDM samples from an XCore port, and delivers frames of audio data over an XCore channel. To this end, an instance of `BasicMicArray` needs to be given the resource IDs of the port to be read and the chanend to transmit frames over. This can be accomplished in either of two ways.

    If the resource IDs for the port and chanend are available as the mic array unit is being allocated, one option is to explicitly construct the `BasicMicArray` instance with the required resource IDs using the two-argument constructor:

        using AppMicArray = mic_array:prefab::BasicMicArray<MICS,SAMPS,DCOE>;
        AppMicArray mics(PORT_PDM_MICS, c_frames_out);

    Otherwise (typically), these can be set using `BasicMicArray::SetPort(port_t)` and `BasicMicArray::SetOutputChannel(chanend_t)` to set the port and channel respectively.

        AppMicArray mics;
        ...
        void app_init(port_t p_pdm_mics, chanend_t c_frames_out)
        {
         mics.SetPort(p_pdm_mics);
         mics.SetOutputChannel(p_pdm_mics);
        }

    Next, the ports and clock block(s) used by the PDM rx service need to be configured appropriately. This is not accomplished directly through the `BasicMicArray` object. Instead, a `pdm_rx_resources_t` struct representing these hardware resources is constructed and passed to `mic_array_resources_configure()`. See the documentation for `pdm_rx_resources_t` and `mic_array_resources_configure()` for more details.

    Finally, if running `BasicMicArray`’s PDM rx service within an ISR, before the mic array unit can be started, the ISR must be installed. This is accomplished with a call to `BasicMicArray::InstallPdmRxISR()`. Installing the ISR will not unmask it.

  Begin Processing (PDM rx ISR)

    After it has been initialized, starting the mic array unit with the PDM rx service running as an ISR, three steps are required.

    First, the PDM clock must be started. This is accomplished with a call to `mic_array_pdm_clock_start()`. The same `pdm_rx_resources_t` that was passed to `mic_array_resources_configure()` is given as an argument here.

    Second, the PDM rx ISR that was installed during initialization must be unmasked. This is accomplished by calling `BasicMicArray::UnmaskPdmRxISR()` on the mic array unit.

    Finally, the mic array processing thread must be started. The entry point for the mic array thread is `BasicMicArray::ThreadEntry()`.

    A typical pattern will include all three of these steps in a single function which wraps the mic array thread entry point.

        AppMicArray mics;
        pdm_rx_resources_t pdm_res;
        ...
        MA_C_API  // alias for 'extern "C"'
        void app_mic_array_task()
        {
         mic_array_pdm_clock_start(&pdm_res);
         mics.UnmaskPdmRxISR();
         mics.ThreadEntry();
        }

    Using this pattern, `app_mic_array_task()` is a C-compatible function which can be called from a multi-tile `main()` in an XC file. Then, `app_mic_array_task()` is called directly from a `par {...}` block. For example,

        main(){
         ...
         par {
           on tile[1]: {
             ... // Do initialization stuff

             par {
               app_mic_array_task();
               ...
               other_thread_on_tile1(); // other threads
             }
           }
         }
        }

  Begin Processing (PDM Rx Thread)

    The procedure for running the mic array unit with the PDM rx component running as a stand-alone thread is much the same with just a couple key differences.

    When running PDM rx as a thread, no call to `BasicMicArray::UnmaskPdmRxISR()` is necessary. Instead, the application spawns a second thread (the first being the mic array processing thread) using `BasicMicArray::PdmRxThreadEntry()` as the entry point.

    `mic_array_pdm_clock_start()` must still be called, but here the requirement is that it be called from the hardware thread on which the PDM rx component is running (which, of course, cannot be the mic array thread).

    A typical application with a multi-tile XC `main()` will provide two C-compatible functions - one for each thread:

        MA_C_API
        void app_pdm_rx_task()
        {
         mic_array_pdm_clock_start(&pdm_res);
         mics.PdmRxThreadEntry();
        }

        MA_C_API
        void app_mic_array_task()
        {
         mics.ThreadEntry();
        }

    Notice that `app_mic_array_task()` above is a thin wrapper for `mics.ThreadEntry()`. Unfortunately, because the type of `mics` is a C++ class, `mics.ThreadEntry()` cannot be called directly from an XC file (including the one containing `main()`). Further, because a C++ class template was used, this library cannot provide a generic C-compatible call wrapper for the methods on a `MicArray` object. This unfortunately means it is necessary in some cases to create a thin wrapper such as `app_mic_array_task()`.

    The threads are spawned from XC main using a `par {...}` block:

        main(){
         ...
         par {
           on tile[1]: {
             ... // Do initialization stuff

             par {
               app_mic_array_task();
               app_pdm_rx_task();
               ...
               other_thread_on_tile1(); // other threads
             }
           }
         }
        }

  Real-Time Constraint

    Once the PDM rx thread is launched or the PDM rx interrupt has been unmasked, PDM data will start being collected and reported to the decimator thread. The application then must start the decimator thread within one output sample time (i.e. sample time for the output of the second stage decimator) to avoid issues.

    Once the mic array processing thread is running, the real-time constraint is active for the thread consuming the mic array unit’s output, and it must waiting to receive an audio frame within one frame time.

  Examples

    This library comes with examples which demonstrate how a mic array unit is used in an actual application. If you are encountering difficulties getting `BasicMicArray` to work, studying the provided examples may help.

  Note

  `BasicMicArray::InstallPdmRxISR()` installs the ISR on the hardware thread that calls the method. In most cases, installing it in the same thread as the decimator is the right choice.

  Template Parameters:

    - MIC_COUNT – Number of microphone channels.

    - FRAME_SIZE – Number of samples in each output audio frame.

    - USE_DCOE – Whether DC offset elimination should be used.

  Public Types

  using TParent = MicArray<MIC_COUNT, TwoStageDecimator<MIC_COUNT, STAGE2_DEC_FACTOR, STAGE2_TAP_COUNT>, StandardPdmRxService<MICS_IN, MIC_COUNT, STAGE2_DEC_FACTOR>, typename std::conditional<USE_DCOE, DcoeSampleFilter<MIC_COUNT>, NopSampleFilter<MIC_COUNT>>::type, FrameOutputHandler<MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitter>>

    `TParent` is an alias for this class template from which this class template inherits.

  Public Functions

  inline constexpr BasicMicArray() noexcept

    No-argument constructor.

    This constructor allocates the mic array and nothing more.

    Call BasicMicArray::Init() to initialize the decimator.

    Subsequent calls to `BasicMicArray::SetPort()` and `BasicMicArray::SetOutputChannel()` will also be required before any processing begins.

  void Init()

    Initialize the decimator.

  BasicMicArray(port_t p_pdm_mics, chanend_t c_frames_out)

    Initialzing constructor.

    If the communication resources required by `BasicMicArray` are known at construction time, this constructor can be used to avoid further initialization steps.

    This constructor does not install the ISR for PDM rx, and so that must be done separately if PDM rx is to be run in interrupt mode.

    Parameters:

      - p_pdm_mics – Port with PDM microphones

      - c_frames_out – (non-streaming) chanend used to transmit frames.

  void SetPort(port_t p_pdm_mics)

    Set the PDM data port.

    This function calls `this->PdmRx.Init(p_pdm_mics)`.

    This should be called during initialization.

    Parameters:

      p_pdm_mics – The port to receive PDM data on.

  void SetOutputChannel(chanend_t c_frames_out)

    Set the audio frame output channel.

    This function calls `this->OutputHandler.FrameTx.SetChannel(c_frames_out)`.

    This must be set prior to entrying the decimator task.

    Parameters:

      c_frames_out – The channel to send audio frames on.

  void PdmRxThreadEntry()

    Entry point for PDM rx thread.

    This function calls `this->PdmRx.ThreadEntry()`.

    Note

    This call does not return.

  void InstallPdmRxISR()

    Install the PDM rx ISR on the calling thread.

    This function calls `this->PdmRx.InstallISR()`.

  void UnmaskPdmRxISR()

    Unmask interrupts on the calling thread.

    This function calls `this->PdmRx.UnmaskISR()`.

## PdmRxService

template<unsigned BLOCK_SIZE, class SubType>
class PdmRxService

  Collects PDM sample data from a port.

  Derivatives of this class template are intended to be used for the `TPdmRx` template parameter of MicArray, where it represents the MicArray::PdmRx component of the mic array.

  An object derived from `PdmRxService` collects blocks of PDM samples from a port and makes them available to the decimation thread as the blocks are completed.

  `PdmRxService` is a base class using CRTP. Subclasses extend `PdmRxService` providing themselves as the template parameter `SubType`.

  This base class provides the logic for aggregating PDM data taken from a port into blocks, and a subclass is required to provide methods `SubType::ReadPort()`, `SubType::SendBlock()` and `SubType::GetPdmBlock()`.

  `SubType::ReadPort()` is responsible for reading 1 word of data from `p_pdm_mics`. See StandardPdmRxService::ReadPort() as an example.

  `SubType::SendBlock()` is provided a block of PDM data as a pointer and is responsible for signaling that to the subsequent processing stage. See StandardPdmRxService::SendBlock() as an example.

  `ReadPort()` and `SendBlock()` are used by `PdmRxService` itself (when running as a thread, rather than ISR).

  `SubType::GetPdmBlock()` responsible for receiving a block of PDM data from `SubType::SendBlock()` as a pointer, deinterleaving the buffer contents, and returning a pointer to the PDM data in the format expected by the mic array unit’s decimator component. See StandardPdmRxService::GetPdmBlock() as an example.

  `GetPdmBlock()` is called by the decimation thread. The pair of functions, `SendBlock()` and `GetPdmBlock()` facilitate inter-thread communication, `SendBlock()` being called by the transmitting end of the communication channel, and `GetPdmBlock()` being called by the receiving end.

  Template Parameters:

    - BLOCK_SIZE – Number of words of PDM data per block.

    - SubType – Subclass of `PdmRxService` actually being used.

  Public Functions

  void SetPort(port_t p_pdm_mics)

    Set the port from which to collect PDM samples.

  void ProcessNext()

    Perform a port read and if a new block has completed, signal.

  void ThreadEntry()

    Entry point for PDM processing thread.

    This function loops forever, calling `ProcessNext()` with each iteration.

  Public Static Attributes

  static constexpr unsigned BlockSize = BLOCK_SIZE

    Number of words of PDM data per block.

    Typically (e.g. TwoStageDecimator) `BLOCK_SIZE` will be exactly the number of words of PDM samples required to produce exactly one new output sample for the mic array unit’s output stream.

    Once `BlockSize` words have been read into one of the block_data, buffers, PDM rx will signal to the decimator thread that new PDM data is available for processing.

### StandardPdmRxService

struct pdm_rx_isr_context_t

  PDM rx interrupt configuration and context.

  Public Members

  port_t p_pdm_mics

    Port on which PDM samples are received.

  uint32_t *pdm_buffer[2]

    Pointers to a pair of buffers used for storing captured PDM samples.

    The buffers themselves are allocated by an instance of mic_array::PdmRxService. The idea is that while the PDM rx ISR is filling one buffer, the decimation thread is busy processing the contents of the other buffer. If the real-time constraint is maintained, the decimation thread will be finished with the contents of its buffer before the PDM rx ISR fills the other buffer. Once full, the PDM rx ISR does a double buffer pointer swap and hands the newly-filled buffer to the decimation thread.

  unsigned phase

    Tracks the completeness of the buffer currently being filled.

    Each read of samples from `p_pdm_mics` gives one word of data. This variable tracks how many more port reads are required before the current buffer has been filled.

  unsigned phase_reset

    The number of words to read from `p_pdn_mics` to fill a buffer.

  chanend_t c_pdm_data

    Streaming chanend the PDM rx ISR uses to signal the decimation thread that another buffer is full and ready to be processed.

    The streaming channel itself is allocated by mic_array::StandardPdmRxService, which owns the other end of the channel.

  unsigned credit

    Used for detecting when the real-time constraint is violated by the decimation thread.

    Each time the decimation thread is given a block of PDM data to process, `credit` is reset to `2`. Each time the PDM rx ISR hands a block of PDM data to the decimation thread, this is decremented.

    Deadlock Condition

      mic_array::StandardPdmRxService uses a streaming channel to facilitate communication between the two execution contexts used by the mic array, the decimation thread and the PDM rx ISR. A streaming channel is used because it allows the contexts to operate asynchronously.

      A channel has a 2 word buffer, and as long as there is room in the buffer, an `OUT` instruction putting a word (in this case, a pointer) into the channel is guaranteed not to block. This is important because the PDM rx ISR is typically configured on the same hardware thread as the decimation thread.

      If a thread is blocked on an `OUT` instruction to a channel, in order to unblock the thread, an `IN` must be issued on the other end of that channel. But because the PDM rx ISR is blocked, it cannot hand control back to the decimation thread, which means the decimation thread can never issue an `IN` instruction to unblock the ISR. The result is a deadlock.

      Unfortunately, there is no way for a thread to query a chanend to determine whether it will block if an `OUT` instruction is issued. That is why `credit` is used. Before issuing an `OUT` to `c_pdm_data`, the PDM rx ISR checks whether `credit` is non-zero. If so, the ISR issues the `OUT` instruction as normal and decrements `credit`.

      If `credit` is zero, the default behavior of PDM rx ISR is to raise an exception (`ET_ECALL`). This reflects the idea that it is generally better if system-breaking errors loudly announce themselves (at least by default). If using mic_array::StandardPdmRxService, this behavior can be changed by passing `false` in a call to mic_array::StandardPdmRxService::AssertOnDroppedBlock(), which will allow blocks of PDM data to be silently dropped (while still avoiding a permanent deadlock).

  unsigned missed_blocks

    Controls and records anti-deadlock behavior.

    If the PDM rx ISR finds that `credit` is `0` when it’s time to send a filled buffer to the decimation thread, it uses `missed_blocks` to control whether the PDM rx ISR should raise an exception or silently drop the block of PDM data.

    If `missed_blocks` is `-1` (its default value) an exception is raised. Otherwise `missed_blocks` is used to record the number of blocks that have been quietly dropped.

pdm_rx_isr_context_t pdm_rx_isr_context

  Configuration and context of the PDM rx ISR when mic_array::StandardPdmRxService is used in interrupt mode.

  `pdm_rx_isr` (`pdm_rx_isr.S`) directly allocates this object as configuration and state parameters required by that interrupt routine.

static inline void enable_pdm_rx_isr(const port_t p_pdm_mics)

  Configure port to use `pdm_rx_isr` as an interrupt routine.

  This function configures `p_pdm_mics` to use `pdm_rx_isr` as its interrupt vector and enables the interrupt on the current hardware thread.

  This function does NOT unmask interrupts.

  Parameters:

    p_pdm_mics – Port resource to enable ISR on.

template<unsigned CHANNELS_IN, unsigned CHANNELS_OUT, unsigned SUBBLOCKS>
class StandardPdmRxService : public mic_array::PdmRxService<CHANNELS_IN * SUBBLOCKS, StandardPdmRxService<CHANNELS_IN, CHANNELS_OUT, SUBBLOCKS>>

  PDM rx service which uses a streaming channel to send a block of data by pointer.

  This class can run the PDM rx service either as a stand-alone thread or through an interrupt.

  Inter-context Transfer

    A streaming channel is used to transfer control of the PDM data block between execution contexts (i.e. thread->thread or ISR->thread).

    The mic array unit receives blocks of PDM data from an instance of this class by calling GetPdmBlock(), which blocks until a new PDM block is available.

  Layouts

    The buffer transferred by `SendBlock()` contains `CHANNELS_IN*SUBBLOCKS` words of PDM data for `CHANNELS_IN` microphone channels. The words are stored in reverse order of arrival.

    See `mic_array::deinterleave_pdm_samples()` for additional details on this format.

    Within `GetPdmBlock()` (i.e. mic array thread) the PDM data block is deinterleaved and copied to another buffer in the format required by the decimator component, which is returned by `GetPdmBlock()`. This buffer contains samples for `CHANNELS_OUT` microphone channels.

  Channel Filtering

    In some cases an application may be required to capture more microphone channels than should actually be processed by subsequent processing stages (including the decimator component). For example, this may be the case if 4 microphone channels are desired but only an 8 bit wide port is physically available to capture the samples.

    This class template has a parameter both for the number of channels to be captured by the port (`CHANNELS_IN`), as well as for the number of channels that are to be output for consumption by the `MicArray`’s decimator component (`CHANNELS_OUT`).

    When the PDM microphones are in an SDR configuration, `CHANNELS_IN` must be the width (in bits) of the XCore port to which the microphones are physically connected. When in a DDR configuration, `CHANNELS_IN` must be twice the width (in bits) of the XCore port to which the microphones are physically connected.

    `CHANNELS_OUT` is the number of microphone channels to be consumed by the mic array’s decimator component (i.e. must be the same as the `MIC_COUNT` template parameter of the decimator component). If all port pins are connected to microphones, this parameter will generally be the same as `CHANNELS_IN`.

  Channel Index (Re-)Mapping

    The input channel index of a microphone depends on the pin to which it is connected. Each pin connected to a port has a bit index for that port, given in the ‘Signal Description and GPIO’ section of your package’s datasheet.

    Suppose an `N`-bit port is used to capture microphone data, and a microphone is connected to bit `B` of that port. In an SDR microphone configuration, the input channel index of that microphone is `B`, the same as the port bit index.

    In a DDR configuration, that microphone will be on either input channel index `B` or `B+N`, depending on whether that microphone is configured for in-phase capture or out-of-phase capture.

    Sometimes it may be desirable to re-order the microphone channel indices. This is likely the case, for example, when `CHANNELS_IN > CHANNELS_OUT`.

    By default output channels are mapped from the input channels with the same index. If `CHANNELS_IN > CHANNELS_OUT`, this means that the input channels with the highest `CHANNELS_IN-CHANNELS_OUT` indices are dropped by default.

    The `MapChannel()` and `MapChannels()` methods can be used to specify a non-default mapping from input channel indices to output channel indices. It takes a pointer to a `CHANNELS_OUT`-element array specifying the input channel index for each output channel.

  Template Parameters:

    - CHANNELS_IN – The number of microphone channels to be captured by the port.

    - CHANNELS_OUT – The number of microphone channels to be delivered by this `StandardPdmRxService` instance.

    - SUBBLOCKS – The number of 32-sample sub-blocks to be captured for each microphone channel.

  Public Functions

  uint32_t ReadPort()

    Read a word of PDM data from the port.

    Returns:

      A `uint32_t` containing 32 PDM samples. If `MIC_COUNT >= 2` the samples from each port will be interleaved together.

  void SendBlock(uint32_t block[CHANNELS_IN * SUBBLOCKS])

    Send a block of PDM data to a listener.

    Parameters:

      block – PDM data to send.

  void Init(port_t p_pdm_mics)

    Initialize this object with a channel and port.

    Parameters:

      p_pdm_mics – Port to receive PDM data on.

  void MapChannels(unsigned map[CHANNELS_OUT])

    Set the input-output mapping for all output channels.

    By default, input channel index `k` maps to output channel index `k`.

    This method overrides that behavior for all channels, re-mapping each output channel such that output channel `k` is derived from input channel `map[k]`.

    Note

    Changing the channel mapping while the mic array unit is running is not recommended.

    Parameters:

      map – Array containing new channel map.

  void MapChannel(unsigned out_channel, unsigned in_channel)

    Set the input-output mapping for a single output channel.

    By default, input channel index `k` maps to output channel index `k`.

    This method overrides that behavior for a single output channel, configuring output channel `out_channel` to be derived from input channel `in_channel`.

    Note

    Changing the channel mapping while the mic array unit is running is not recommended.

    Parameters:

      - out_channel – Output channel index to be re-mapped.

      - in_channel – New source channel index for `out_channel`.

  void InstallISR()

    Install ISR for PDM reception on the current core.

    Note

    This does not unmask interrupts.

  void UnmaskISR()

    Unmask interrupts on the current core.

  uint32_t *GetPdmBlock()

    Get a block of PDM data.

    Because blocks of PDM samples are delivered by pointer, the caller must either copy the samples or finish processing them before the next block of samples is ready, or the data will be clobbered.

    Note

    This is a blocking call.

    Returns:

      Pointer to block of PDM data.

  void AssertOnDroppedBlock(bool doAssert)

    Set whether dropped PDM samples should cause an assertion.

    If `doAssert` is set to `true` (default), the PDM rx ISR will raise an exception (`ET_CALL`) if it is ready to deliver a PDM block to the mic array thread when the mic array thread is not ready to receive it. If `false`, dropped blocks can be tracked through `pdm_rx_isr_context.missed_blocks`.

## TwoStageDecimator

template<unsigned MIC_COUNT, unsigned S2_DEC_FACTOR, unsigned S2_TAP_COUNT>
class TwoStageDecimator

  First and Second Stage Decimator.

  This class template represents a two stage decimator which converts a stream of PDM samples to a lower sample rate stream of PCM samples.

  Concrete implementations of this class template are meant to be used as the `TDecimator` template parameter in the MicArray class template.

  Template Parameters:

    - MIC_COUNT – Number of microphone channels.

    - S2_DEC_FACTOR – Stage 2 decimation factor.

    - S2_TAP_COUNT – Stage 2 tap count.

  Public Functions

  void Init(const uint32_t *s1_filter_coef, const int32_t *s2_filter_coef, const right_shift_t s2_filter_shr)

    Initialize the decimator.

    Sets the stage 1 and 2 filter coefficients. The decimator must be initialized before any calls to `ProcessBlock()`.

    `s1_filter_coef` points to a block of coefficients for the first stage decimator. This library provides coefficients for the first stage decimator; see `mic_array/etc/filters_default.h`.

    `s2_filter_coef` points to an array of coefficients for the second stage decimator. This library provides coefficients for the second stage decimator where the second stage decimation factor is 6; see `mic_array/etc/filters_default.h`.

    `s2_filter_shr` is the final right-shift applied to the stage 2 filter’s accumulator prior to output. See lib_xcore_math’s documentation of `filter_fir_s32_t` for more details.

    Parameters:

      - s1_filter_coef –

        Stage 1 filter coefficients.

        This points to a block of coefficients for the first stage decimator. This library provides coefficients for the first stage decimator.

        See `stage1_coef`.

      - s2_filter_coef –

        Stage 2 filter coefficients.

        This points to a block of coefficients for the second stage decimator. This library provides coefficients for the second stage decimator.

        See `stage2_coef`.

      - s2_filter_shr –

        Stage 2 filter right-shift.

        This is the output shift used by the second stage decimator.

        See `stage2_shr`.

  void ProcessBlock(int32_t sample_out[MIC_COUNT], uint32_t pdm_block[BLOCK_SIZE])

    Process one block of PDM data.

    Processes a block of PDM data to produce an output sample from the second stage decimator.

    `pdm_block` contains exactly enough PDM samples to produce a single output sample from the second stage decimator. The layout of `pdm_block` should (effectively) be:

        struct {
          struct {
            // lower word indices are older samples.
            // less significant bits in a word are older samples.
            uint32_t samples[S2_DEC_FACTOR];
          } microphone[MIC_COUNT]; // mic channels are in ascending order
        } pdm_block;

    A single output sample from the second stage decimator is computed and written to `sample_out[]`.

    Parameters:

      - sample_out – Output sample vector.

      - pdm_block – PDM data to be processed.

  Public Members

  unsigned DecimationFactor = S2_DEC_FACTOR

    Stage 2 decimator decimation factor.

  unsigned TapCount = S2_TAP_COUNT

    Stage 2 decimator tap count.

  const uint32_t *filter_coef

    Pointer to filter coefficients for Stage 1

  uint32_t pdm_history[MIC_COUNT][8]

    Filter state (PDM history) for stage 1 filters.

  filter_fir_s32_t filters[MIC_COUNT]

    Stage 2 FIR filters

  int32_t filter_state[MIC_COUNT][S2_TAP_COUNT] = {{0}}

    Stage 2 filter stage.

  Public Static Attributes

  static constexpr unsigned BLOCK_SIZE = MIC_COUNT * S2_DEC_FACTOR

    Size of a block of PDM data in words.

  static constexpr unsigned MicCount = MIC_COUNT

    Number of microphone channels.

  static const struct mic_array::TwoStageDecimator::[anonymous] Stage2

    Stage 2 decimator parameters

## SampleFilter

### NopSampleFilter

template<unsigned MIC_COUNT>
class NopSampleFilter

  SampleFilter which does nothing.

  To be used as the `TSampleFilter` template parameter of MicArray when no post-decimation filtering is desired.

  Calls to `NopSampleFilter::Filter()` are intended to be optimized out at compile time.

  Template Parameters:

    MIC_COUNT – Number of microphone channels.

  Public Functions

  inline void Filter(int32_t sample[MIC_COUNT])

    Do nothing.

### DcoeSampleFilter

template<unsigned MIC_COUNT>
class DcoeSampleFilter

  Filter which applies DC Offset Elimination (DCOE).

  To be used as the `TSampleFilter` template parameter of MicArray when DCOE is desired as post-processing after the decimation filter.

  The filter is a simple first-order IIR filter which applies the following filter equation:

      R = 252.0 / 256.0
      y[t] = R * y[t-1] + x[t] - x[t-1]

  Template Parameters:

    MIC_COUNT – Number of microphone channels.

  Public Functions

  void Init()

    Initialize the filter states.

    The filter states must be initialized prior to calls to `Filter()`.

  void Filter(int32_t sample[MIC_COUNT])

    Apply DCOE filter on samples.

    `sample` is an array of samples to be filtered, and is updated in-place.

    The filter states must have been initialized with a call to `Init()` prior to calling this function.

    Parameters:

      sample – Samples to be filtered. Updated in-place.

## OutputHandler

An OutputHandler is a class which meets the requirements to be used as the `TOutputHandler` template parameter of the `MicArray` class template. The basic requirement is that it have a method:

This method is how the mic array communicates its output with the rest of the application’s audio processing pipeline. MicArray calls this method once for each mic array output sample.

See `MicArray::OutputHandler` for more details.

### FrameOutputHandler

template<unsigned MIC_COUNT, unsigned SAMPLE_COUNT, template<unsigned, unsigned> class FrameTransmitter, unsigned FRAME_COUNT = 1>
class FrameOutputHandler

  OutputHandler implementation which groups samples into non-overlapping multi-sample audio frames and sends entire frames to subsequent processing stages.

  This class template can be used as an OutputHandler with the MicArray class template. See MicArray::OutputHandler.

  Classes derived from this template collect samples into frames. A frame is a 2 dimensional array with one index corresponding to the audio channel and the other index corresponding to time step, e.g.:

      int32_t frame[MIC_COUNT][SAMPLE_COUNT];

  Each call to OutputSample() adds the sample to the current frame, and then iff the frame is full, uses its FrameTx component to transfer the frame of audio to subsequent processing stages. Only one of every `SAMPLE_COUNT` calls to OutputSample() results in an actual transmission to subsequent stages.

  With `FrameOutputHandler`, the thread receiving the audio will generally need to know how many microphone channels and how many samples to expect per frame (although, strictly speaking, that depends upon the chosen `FrameTransmitter` implementation).

  Template Parameters:

    - MIC_COUNT –

      The number of audio channels in each sample and each frame.

    - SAMPLE_COUNT – Number of samples per frame.

      The `SAMPLE_COUNT` template parameter is the number of samples assembled into each audio frame. Only completed frames are transmitted to subsequent processing stages. A `SAMPLE_COUNT` value of `1` effectively disables framing, transmitting one sample for each call made to OutputSample.

    - FrameTransmitter –

      The concrete type of the FrameTx component of this class.

      Like many classes in this library, FrameOutputHandler uses the Curiously Recurring Template Pattern.

    - FRAME_COUNT –

      The number of frame buffers an instance of `FrameOutputHandler` should cycle through. Unless audio frames are communicated with subsequent processing stages through shared memory, the default value of `1` is usualy ideal.

  Public Functions

  inline FrameOutputHandler()

    Construct new `FrameOutputHandler`.

    The default no-argument constructor for `FrameTransmitter` is used to create `FrameTx`.

  inline FrameOutputHandler(FrameTransmitter<MIC_COUNT, SAMPLE_COUNT> frame_tx)

    Construct new `FrameOutputHandler`.

    Uses the provided FrameTransmitter to send frames.

    Parameters:

      frame_tx – Frame transmitter for sending frames.

  void OutputSample(int32_t sample[MIC_COUNT])

    Add new sample to current frame and output frame if filled.

    Parameters:

      sample – Sample to be added to current frame.

  Public Members

  FrameTransmitter<MIC_COUNT, SAMPLE_COUNT> FrameTx

    `FrameTransmitter` used to transmit frames to the next stage for processing.

    `FrameTransmitter` is the CRTP type template parameter used in this class to control how frames of audio data are communicated with subsequent pipeline stages.

    The type supplied for `FrameTransmitter` must be a class template with two integer template parameters, corresponding to this class’s `MIC_COUNT` and `SAMPLE_COUNT` template parameters respectively, indicating the shape of the frame object to be transmitted.

    The `FrameTransmitter` type is required to implement a single method:

        void OutputFrame(int32_t frame[MIC_COUNT][SAMPLE_COUNT]);

    `OutputFrame()` is called once for each completed audio frame and is responsible for the details of how the frame’s data gets communicated to subsequent stages. For example, the ChannelFrameTransmitter class template uses an XCore channel to send samples to another thread (by value).

    Alternative implementations might use shared memory or an RTOS queue to transmit the frame data, or might even use a port to signal the samples directly to an external DAC.

#### ChannelFrameTransmitter

template<unsigned MIC_COUNT, unsigned SAMPLE_COUNT>
class ChannelFrameTransmitter

  Frame transmitter which transmits frame over a channel.

  This class template is meant for use as the `FrameTransmitter` template parameter of FrameOutputHandler.

  When using this frame transmitter, frames are transmitted over a channel using the frame transfer API in `mic_array/frame_transfer.h`.

  Usually, a call to `ma_frame_rx()` (with the other end of c_frame_out as argument) should be used to receive the frame on another thread.

  If the receiving thread is not waiting to receive the frame when OutputFrame() is called, that method will block until the frame has been transmitted. In order to ensure there are no violations of the mic array’s real-time constraints, the receiver should be ready to receive a frame as soon as it becomes available.

  Frames can be transmitted between tiles using this class.

  Note

  While OutputFrame() is blocking, it will not prevent the PDM rx interrupt from firing.

  Template Parameters:

    - MIC_COUNT – Number of audio channels in each frame.

    - SAMPLE_COUNT – Number of samples per frame.

  Public Functions

  inline ChannelFrameTransmitter()

    Construct a `ChannelFrameTransmitter`.

    If this constructor is used, SetChannel() must be called to configure the channel over which frames are transmitted prior to any calls to OutputFrame().

  inline ChannelFrameTransmitter(chanend_t c_frame_out)

    Construct a `ChannelFrameTransmitter`.

    The supplied value of `c_frame_out` must be a valid chanend.

    Parameters:

      c_frame_out – Chanend over which frames will be transmitted.

  void SetChannel(chanend_t c_frame_out)

    Set channel used for frame transfers.

    The supplied value of `c_frame_out` must be a valid chanend.

    Parameters:

      c_frame_out – Chanend over which frames will be transmitted.

  chanend_t GetChannel()

    Get the chanend used for frame transfers.

    Returns:

      Channel to be used for frame transfers.

  void OutputFrame(int32_t frame[MIC_COUNT][SAMPLE_COUNT])

    Transmit the specified frame.

    See ChannelFrameTransmitter for additional details.

    Parameters:

      frame – Frame to be transmitted.

## Misc

template<unsigned MIC_COUNT>
void mic_array::deinterleave_pdm_samples(uint32_t *samples, unsigned s2_dec_factor)

  Deinterleave the channels of a block of PDM data.

  PDM samples received on a port are shifted into a 32-bit buffer in such a way that the samples for each microphone channel are all interleaved with one another. The first stage decimator, however, requires these to be separated.

  `samples` must point to a buffer containing `(MIC_COUNT*s2_dec_factor)` words of PDM data. Because the decimation factor for the first stage decimator is a fixed value of `32`, `32` PDM samples from each microphone is enough to produce one output sample (a `MIC_COUNT`-element vector) from the first stage decimator. `32*s2_dec_factor` PDM samples for each of the `MIC_COUNT` microphone channels is then exactly what is required to produce a single output sample from the second stage decimator.

  The PDM data will be deinterleaved in-place.

  On input, the format of the buffer to which `samples` points is assumed to be such that the following function will extract (only) the `k`th sample for microphone channel `n` (where `k` is a time index, not a memory index):

  Input Format

      unsigned get_sample(uint32_t* samples,
                          unsigned MIC_COUNT, unsigned s2_dec_factor,
                          unsigned n, unsigned k)
      {
        const end_word = MIC_COUNT * s2_dec_factor - 1; // chronologically first
        const unsigned samp_per_word = 32 / MIC_COUNT;
        const words_from_end = k / samp_per_word;
        const uint32_t word_val = samples[end_word-words_from_end];
        const unsigned bit_offset = (k % end_word) + n;
        return (word_val >> bit_offset) & 1;
      }

  Here, the words of `samples` are stored in reverse order (older samples are at higher word indices), and within a word the oldest samples are the least significant bits. The LSb of a word is always microphone channel `0`, and the MSb of a word is always microphone channel `MIC_COUNT-1`.

  Upon return, the format of the buffer to which samples points will be such that the following function will extract (only) the `k`th sample for microphone channel `n`:

  Output Format

      unsigned get_sample(uint32_t* samples,
                          unsigned MIC_COUNT, unsigned s2_dec_factor,
                          unsigned n, unsigned k)
      {
        const unsigned subblock = (s2_dec_factor-1)-(k/32);
        const unsigned word_val = samples[subblock * MIC_COUNT + n];
        return (word_val >> (k%32)) & 1;
      }

  Here, each word contains samples from only a single channel, with words at higher addresses containing older samples. `samples[0]` contains the newest samples for microphone channel `0`, and `samples[MIC_COUNT-1]` contains the newest samples for microphone channel `MIC_COUNT-1`. `samples[MIC_COUNT]` contains the next-oldest set of samples for channel `0`, and so on.

  Template Parameters:

    MIC_COUNT –

    Number of channels represented in PDM data.

    One of

    `{1,2,4,8}`
  Parameters:

    - samples – Pointer to block of PDM samples.

    - s2_dec_factor – Stage2 decimator decimation factor.