Last Details

In the previous section we implemented a basic granular synthesis voice transformer that lowers the pitch of the input voice. In this section we will address some remaining issues, namely:

  • implement an effect that raises the pitch of the voice (aka the "Chipmunks" effect)

  • properly initialize the buffer as a function of the pitch change

  • optimize the code a little more

The Chipmunks

To raise the pitch of the voice we need to set α\alpha to values larger than one. As we have seen, this makes the effect noncausal, which we need to address by introducing some processing delay.

The way to achieve this is to place the audio buffer's input index forward with respect to the output index; let's do this properly by creating an initialization function for the buffer that takes the resampling factor as the input.

TASK 1: Determine the proper initial value for buf_ix when α>1\alpha > 1 in the function below.

static void InitBuffer(float Alpha) {
  memset(buffer, 0, BUF_LEN * sizeof(int16_t));

  alpha = (int32_t)(0x7FFF * Alpha);
  // input index for inserting DMA data
  if (Alpha <= 1)
      buf_ix = 0;
    buf_ix = ...;

  prev_ix = BUF_LEN - GRAIN_STRIDE;
  curr_ix = 0;
  grain_m = 0;

By now you know where to place this code but don't forget to

  • add the following line to the file main.h between the /* USER CODE BEGIN Includes */ tags.

    #include <memory.h>
  • declare the function prototype in the USER CODE BEGIN PFP block

  • call the function before launching the DMA transfers:

    InitBuffer(3.0 / 2.0);
    // begin DMAs
    HAL_I2S_Transmit_DMA(&hi2s1, (uint16_t *) dma_tx, FULL_BUFFER_SIZE);
    HAL_I2S_Receive_DMA(&hi2s2, (uint16_t *) dma_rx, FULL_BUFFER_SIZE);

Switching between effects

We can use the blue button on the Nucleo board to switch between Darth Vader and the Chipmunks; to do so, define the following constants at the beginning of the code

#define DARTH (2.0 / 3.0)
#define CHIPMUNK (3.0 / 2.0)

and modify the user button callback like so:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  if (GPIO_Pin == B1_Pin) {
    // blue button pressed
    if (user_button) {
      user_button = 0;
      HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
    } else {
      user_button = 1;
      HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);

Final optimizations

In the main processing loop, we are performing two checks on the value of grain_m per output sample. However, in the current implementation, both the stride and the taper lengths are multiples of the size of the DMA half-buffer. This allows us to move these checks outside of the processing loop and perform them once per call rather than once per sample

TASK 2: Modify the VoiceEffect() function to reduce the number of if statements per call. Benchmark the result and observe the change in performance.


Are you ready to see the answers ? :)

Last updated