Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 7 of 7

Thread: Audio Effect Delay Pops & Clicks when changing tap times

  1. #1
    Senior Member houtson's Avatar
    Join Date
    Aug 2015
    Location
    Scotland
    Posts
    132

    Audio Effect Delay Pops & Clicks when changing tap times

    Hi

    I'm using the AudioEffectDelay with a Teensy4 and AudioBoard.

    When I change tap times I get pops and clicks. I'm changing a few tap time together (like changing between different presets if that makes sense).

    Question is whether this is as expected and if so are there any approaches to minimise the noise?

    I've attached some simple code that switches between two sets of tap delay times (it expects some audio input through the audioboard).

    Any advice appreciated, Cheers Paul




    Code:
    #include <Audio.h>
    #include <SD.h>
    #include <SPI.h>
    #include <SerialFlash.h>
    #include <Wire.h>
    
    // GUItool: begin automatically generated code
    AudioInputI2S i2s1;       // xy=206,94
    AudioMixer4 mixer1;       // xy=523,76
    AudioEffectDelay delay1;  // xy=538,236
    AudioMixer4 mixer2;       // xy=766,197
    AudioOutputI2S i2s2;      // xy=1099,196
    AudioConnection patchCord1(i2s1, 0, mixer1, 0);
    AudioConnection patchCord2(i2s1, 1, mixer1, 1);
    AudioConnection patchCord3(mixer1, delay1);
    AudioConnection patchCord4(mixer1, 0, mixer2, 2);
    AudioConnection patchCord5(delay1, 0, mixer2, 0);
    AudioConnection patchCord6(delay1, 1, mixer2, 1);
    AudioConnection patchCord7(delay1, 2, mixer1, 2);
    AudioConnection patchCord8(mixer2, 0, i2s2, 0);
    AudioConnection patchCord9(mixer2, 0, i2s2, 1);
    AudioControlSGTL5000 codec;  // xy=377,390
    // GUItool: end automatically generated code
    
    
    void setup(void) {
      Serial.begin(9600);
      AudioMemory(150 * (128 / AUDIO_BLOCK_SAMPLES));
      codec.enable();
      codec.volume(0.5);
    
      mixer1.gain(0, 0.7);
      mixer1.gain(1, 0.7);
      mixer1.gain(2, 0.5);
      mixer1.gain(3, 0.0);
    }
    
    void loop(void) {
      Serial.println("Delay 1");
      delay1.delay(0, 50);
      delay1.delay(1, 75);
      delay1.delay(2, 400);
      delay(2000);
      Serial.println("Delay 2");
      delay1.delay(0, 150);
      delay1.delay(1, 250);
      delay1.delay(2, 400);
      delay(2000);
    }

  2. #2
    Senior Member
    Join Date
    Jul 2020
    Posts
    398
    Quote Originally Posted by houtson View Post
    Hi

    I'm using the AudioEffectDelay with a Teensy4 and AudioBoard.

    When I change tap times I get pops and clicks. I'm changing a few tap time together (like changing between different presets if that makes sense).

    Question is whether this is as expected and if so are there any approaches to minimise the noise?
    Yes, its completely expected as you are making discontinuous changes to a signal on the fly.

    One approach often used is to switch on a zero-crossing, but the Audio lib processes in blocks so there's no
    easy way to do this, and it won't work for several taps at once as the zero crossings happen at different
    times.

    Using linear interpolation between old and new taps might work, so you'd need two sets of taps, and fade between them in
    a few ms.

  3. #3
    Senior Member houtson's Avatar
    Join Date
    Aug 2015
    Location
    Scotland
    Posts
    132
    Hi Mark,

    I thought that might the be the expected behaviour, thanks for confirming and your suggestion - I'll have a go at your second suggestion (cross fading two sets of taps)

    cheers, Paul

  4. #4
    Senior Member houtson's Avatar
    Join Date
    Aug 2015
    Location
    Scotland
    Posts
    132
    Thanks again MarkT for the ideas, I made a new delay object to get round the clicks and pops when you change delay times dynamically.
    It seems to work well letting me adjust multiple taps. It's not the most efficient code but works for my situation.

    A few details:
    - the new object works sample by sample rather than by block, fading between old and new delay times per tap to avoid clicks and pops
    - it's less efficient than the standard block based effect but easier to work with
    - it doesn't use the AudioMemory for the delay line, you need to pass it a basic array. this means it won't re-size if you don't use the full delay
    - it's multi-tap with 10 independent taps
    - when you want to change delay time you call delayfade(uint8_t channel, float milliseconds, float transition_time); transition time is the time to fade in millis.
    - it's a good basis for other tape delay type effects

    if anyone is looking for something similar let me know and I'll post the code and an example

    cheers, Paul

  5. #5
    Senior Member ETMoody3's Avatar
    Join Date
    Mar 2014
    Location
    New Ulm, Mn
    Posts
    146
    I'd be interested in trying this, thanks in advance for your effort

  6. #6
    Senior Member houtson's Avatar
    Join Date
    Aug 2015
    Location
    Scotland
    Posts
    132
    Hi ETMoody3

    I've pasted below:
    - example file (just like the first one in this post that changes a few taps over and over)
    - effect_tapedelay10tap.h which is the header file for the effect object
    - effect_tapedelay10tap.cpp which is the main file for the effect object

    I'm not a coder so any suggestions welcome - it uses a bit of float math for the fade which I'm sure could be optimised for integers. I've been using a Teensy 4.

    Hopefully straight forward, any feedback or questions welcome.

    Cheers Paul

    example.ino
    Code:
    #include <Audio.h>
    #include <SD.h>
    #include <SPI.h>
    #include <SerialFlash.h>
    #include <Wire.h>
    #include "effect_tapedelay10tap.h"
    
    // GUItool: begin automatically generated code
    AudioInputI2S i2s1;       // xy=206,94
    AudioMixer4 mixer1;       // xy=523,76
    AudioEffectTapeDelay10tap delay1;  // xy=538,236
    AudioMixer4 mixer2;       // xy=766,197
    AudioOutputI2S i2s2;      // xy=1099,196
    AudioConnection patchCord1(i2s1, 0, mixer1, 0);
    AudioConnection patchCord2(i2s1, 1, mixer1, 1);
    AudioConnection patchCord3(mixer1, delay1);
    AudioConnection patchCord4(mixer1, 0, mixer2, 2);
    AudioConnection patchCord5(delay1, 0, mixer2, 0);
    AudioConnection patchCord6(delay1, 1, mixer2, 1);
    AudioConnection patchCord7(delay1, 2, mixer1, 2);
    AudioConnection patchCord8(mixer2, 0, i2s2, 0);
    AudioConnection patchCord9(mixer2, 0, i2s2, 1);
    AudioControlSGTL5000 codec;  // xy=377,390
    // GUItool: end automatically generated code
    
    #define DELAY_MAX_LEN 22050  // buffer for samples @44100 samples per second, 22050 = 0.5s
    int16_t sample_delay_line[DELAY_MAX_LEN] = {};
    
    void setup(void) {
      Serial.begin(9600);
      AudioMemory(150 * (128 / AUDIO_BLOCK_SAMPLES));
      codec.enable();
      codec.volume(0.5);
    
      mixer1.gain(0, 0.7);
      mixer1.gain(1, 0.7);
      mixer1.gain(2, 0.5);
      mixer1.gain(3, 0.0);
    
      // initialise the delayline
      delay1.begin(sample_delay_line, DELAY_MAX_LEN);
    }
    
    void loop(void) {
      Serial.println("Delay 1");
      delay1.delayfade(0, 6, 3.0);  // (tap_number 0-9, millis of delay, milliseconds to tranistion from old to new time)
      delay1.delayfade(1, 12, 3.0);
      delay1.delayfade(2, 18, 3.0);
      delay(2000);
      Serial.println("Delay 2");
      delay1.delayfade(0, 150, 3.0);
      delay1.delayfade(1, 250, 3.0);
      delay1.delayfade(2, 350, 3.0);
      delay(2000);
    }

    effect_tapedelay10tap.h

    Code:
    /* Audio Library for Teensy 3.X
     * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
     *
     * Development of this audio library was funded by PJRC.COM, LLC by sales of
     * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
     * open source software by purchasing Teensy or other PJRC products.
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice, development funding notice, and this permission
     * notice shall be included in all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     * THE SOFTWARE.
     *
     * Modified to extend to 10 taps PMF 16-03-2020
     * Modified for single samples delay line (rather than blocks) and tape delay like behaviour PMF 02-09-2020
     * added delayfade to fade between old and new delay time PMF 04-09-2020
     */
    
    #ifndef effect_tapedelay10tap_h_
    #define effect_tapedelay10tap_h_
    #include "Arduino.h"
    #include "AudioStream.h"
    
    #define DELAY_NUM_TAPS 10  // max numer of taps / channels
    
    class AudioEffectTapeDelay10tap : public AudioStream {
     public:
      AudioEffectTapeDelay10tap(void) : AudioStream(1, inputQueueArray) {}
      // initialise the delay line
      void begin(int16_t *delay_line, uint32_t max_delay_length);
      // activate a tap and/or change time with a fade between old and new time (no clicks), transition time in millis
      uint32_t delayfade(uint8_t channel, float milliseconds, float transition_time);
      // disable a tap
      void disable(uint8_t channel);
      // main update routine
      virtual void update(void);
    
     private:
      audio_block_t *inputQueueArray[1];
      uint32_t delay_samples[DELAY_NUM_TAPS];          // actual # of sample delay for each channel
      uint32_t desired_delay_samples[DELAY_NUM_TAPS];  // desired # of sample delay for each channel
      int16_t fade_length_samples[DELAY_NUM_TAPS];     // how long to fade in samples
      int16_t fade_count[DELAY_NUM_TAPS];              // count down for fades
      uint32_t max_delay_length_samples;               // lenght of the delay line in samples
      uint32_t write_index;                            // write head position
      uint16_t activemask;                             // which taps/channels are active
      int16_t *sample_delay_line;                      // pointer to delay line
    };
    #endif
    effect_tapedelay10tap.cpp

    Code:
    /* Audio Library for Teensy 3.X
     * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
     *
     * Development of this audio library was funded by PJRC.COM, LLC by sales of
     * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
     * open source software by purchasing Teensy or other PJRC products.
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice, development funding notice, and this permission
     * notice shall be included in all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     * THE SOFTWARE.
     *
     *
     * Modified to extend to 10 taps PMF 16-03-2020
     * Modified for single samples delay line (rather than blocks) and tape delay like behaviour PMF 02-09-2020
     * added delayfade to fade between old and new delay time PMF 04-09-2020
     */
    #include "effect_tapedelay10tap.h"
    
    #include <Arduino.h>
    
    void AudioEffectTapeDelay10tap::begin(int16_t *delay_line, uint32_t max_delay_length) {
      sample_delay_line = delay_line;
      max_delay_length_samples = max_delay_length - 1;
      write_index = 0;
    }
    
    // activate a tap and/or change time with a fade between old and new time (no clicks), transition time in millis
    uint32_t AudioEffectTapeDelay10tap::delayfade(uint8_t channel, float milliseconds, float transition_time) {
      if (channel >= DELAY_NUM_TAPS) return 0;
      if (milliseconds < 0.0) {
        milliseconds = 0.0;
      }
      if (transition_time < 0.0) transition_time = 0.0;
      uint32_t delay_length_samples = (milliseconds * (AUDIO_SAMPLE_RATE_EXACT / 1000.0)) + 0.5;
      if (delay_length_samples > max_delay_length_samples) delay_length_samples = max_delay_length_samples;
      fade_length_samples[channel] = fade_count[channel] = (transition_time * (AUDIO_SAMPLE_RATE_EXACT / 1000.0)) + 0.5;
      // enable disabled channel
      if (!(activemask & (1 << channel))) {
        desired_delay_samples[channel] = delay_samples[channel] = delay_length_samples;
        fade_count[channel] = 0;
        activemask |= (1 << channel);
      } else {
        desired_delay_samples[channel] = delay_length_samples;
      }
      return delay_samples[channel];
    }
    
    void AudioEffectTapeDelay10tap::disable(uint8_t channel) {
      if (channel >= DELAY_NUM_TAPS) return;
      // disable this channel
      activemask &= ~(1 << channel);
    };
    
    void AudioEffectTapeDelay10tap::update(void) {
      audio_block_t *input, *output;
      int16_t *input_data_pointer, *output_data_pointer;
      uint32_t read_index, desired_read_index, start_index;
      uint8_t channel;
      float fade_multiplier;
    
      if (sample_delay_line == NULL) return;
      // grab a copy of the write_index starting poisition
      start_index = write_index;
    
      // write incoming block of samples to buffer
      input = receiveReadOnly();
      if (input) {
        input_data_pointer = input->data;
        for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
          sample_delay_line[write_index++] = *input_data_pointer++;
          if (write_index >= max_delay_length_samples) write_index = 0;
        }
        release(input);
      }
    
      // process each tap and write out delayed samples
      for (channel = 0; channel < DELAY_NUM_TAPS; channel++) {
        // check if channel is active
        if (!(activemask & (1 << channel))) continue;
        output = allocate();
        if (!output) continue;
        output_data_pointer = output->data;
        // position the read head for this channel / tap
        read_index = ((start_index - delay_samples[channel] + max_delay_length_samples) % max_delay_length_samples);
        // if fading between current delay to desired , position desired read head
        if (fade_count[channel] > 0) desired_read_index = ((start_index - desired_delay_samples[channel] + max_delay_length_samples) % max_delay_length_samples);
        for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
          // if fading, cross mix in fade_length_samples steps
          if (fade_count[channel] > 0) {
            // calculate a multipier for the fade
            fade_multiplier = (float)fade_count[channel] / fade_length_samples[channel];
            // read the two points from the delay line, crossfade and send to the output block
            *output_data_pointer++ =
                (int16_t)(sample_delay_line[read_index] * fade_multiplier) + (int16_t)(sample_delay_line[desired_read_index] * (1 - fade_multiplier));
            fade_count[channel]--;
            if (fade_count[channel] == 0) {
              delay_samples[channel] = desired_delay_samples[channel];
              read_index = desired_read_index;
            }
            // increment and wrap around the desired_read_index
            desired_read_index++;
            if (desired_read_index >= max_delay_length_samples) desired_read_index = 0;
          } else {
            // read delay line and send sample to the output block
            *output_data_pointer++ = sample_delay_line[read_index];
          }
          // increment and wrap around the main read index
          read_index++;
          if (read_index >= max_delay_length_samples) read_index = 0;
        }
        transmit(output, channel);
        release(output);
      }
    }

  7. #7
    Senior Member ETMoody3's Avatar
    Join Date
    Mar 2014
    Location
    New Ulm, Mn
    Posts
    146
    Nice.

    I just put the delay array in external PSRAM on a Teensy4.1.

    Code:
    EXTMEM int16_t sample_delay_line[DELAY_MAX_LEN] = {};
    Useful.

    Thank you

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •