EXAMPLE Rhythmic delay

sonic_explorer

Active member
I took the MultiDelay effect @tcpoint ported over from the DaisyExamples and turned it into a rhythmic delay which has 4 individual delays with a fixed spacing and a single global delay time control. Each delay can be individually turned on/off with the switches. The delays still sound digital and don't have the feel/mojo of the Echorec delays, but the spacing is the same (1/16 note, 1/8 note, dotted 1/8 note, 1/4 note). Here is the code directory which includes a compiled .bin file in the build subdirectory you can just flash to the board without having to compile the program yourself.

I marked this as an example because I included a lot of comments in the code to explain what was happening (.....mostly for myself as a novice C++ user). I used a few of the DaisyExamples modules that I think are going to be useful for future projects:
- Crossfade: The wet/dry mix uses the crossfade function so that a constant volume is maintained throughout the mix sweep.
- LED objects: I used led objects which do a lot of the led initialing for you and I find nicer to work with than the longer dsy_gpio_XXX commands. With led objects you can set the brightness of a led with led1.Set(brightness_value).

I list a few easy & advanced ways to modify this delay in the README file, but suggestions are very welcome too.

I have a version of this delay that has a tone control for the repeats and where the second footswitch causes runaway feedback while pressed, but I am still tweaking it a bit. I will post a comment on this thread when it is a bit more done. It is posted here now, but is still a work in progress. (This more featured version uses the Tone (low pass), ATone (high pass), and Balance DaisyExample modules which work well.)

One thing I found extremely helpful while debugging the program was using the JTAG pins on the Daisy via a STlink-mini-V3 and openocd to quickly flash the program to the Daisy. The super useful part of this is the very last line on this page on the Dasiy wiki. When you have the STlink-mini-V3 connected you can just type 'make program' (at least on linux/OS) to flash a program to the Daisy without having a separate micro-USB hooked up to the Daisy or have to press the reset & reboot buttons. This also allows the Daisy to be flashed within an enclosure with just a small slot filed away from the enclosure lid for the JTAG ribbon cable. I have included pictures below showing this.

combined_Daisy_small.png
 
Last edited:
Thanks for posting this! Gonna test it out when I get the chance. I appreciate all the comments in the code, very helpful for a newb like me
 
This is great. I completely ignored the Led objects because I assumed they were written for the I2C LED driver. So you're getting variable brightness as well?

I have a little something to add to this code later, as soon as I get a few minutes.
 
Yea, it is super easy to vary the brightness of the led with the Led object.

I have added my more advanced rhythm_delay to that same repo (here) - which is still a work in progress. In it when footswitch_2 is pressed the feedback is ramped up (& down when released) and the brightness of led2 shows how much higher the current level of feedback is over the knob value. The tone control over the repeats in this version is unnecessarily drastic, but because this can be such a busy delay it can really help the delays move into the background.
 
Last edited:
Thanks for this and the commenting in the cpp file is FANTASTIC !

I will have to check how to use one of the footswitch to make it a Hold/Loop switch. :)

I'm very grateful as a not-very-a-coder seeing the coding community growing around the Terrarium.
 
I've been trying to use the LED object to fade an LED in and out with an envelope filter, but have had no luck. Has anyone else been able to get LEDs to pulse? If so, what is your special sauce?
 
I have not tried to control an LED with an envelope filter, but here is some code I wrote when testing things to control led2 with a sine oscillator with the speed controlled by knob_6:

Code:
#include "daisysp.h"
#include "daisy_petal.h"
#include "terrarium.h"

using namespace daisy;
using namespace daisysp;
using namespace terrarium;

DaisyPetal petal;
static Oscillator osc_trem;
float trem_tone;
Parameter freqParam;
Led led1, led2;

void ProcessControls();

static void AudioCallback(float **in, float **out, size_t size)
{
    ProcessControls();
    for(size_t i = 0; i < size; i++)
        in[0][i] = out[0][i];
}

int main(void)
{
    float samplerate;
    petal.Init(); // Initialize hardware (daisy petal, and petal.seed)                                                                                                                             
    samplerate = petal.AudioSampleRate();
    osc_trem.Init(samplerate);

    led1.Init(petal.seed.GetPin(Terrarium::LED_1),false);
    led2.Init(petal.seed.GetPin(Terrarium::LED_2),false);

    petal.StartAdc();
    petal.StartAudio(AudioCallback);
}

void ProcessControls()
{
    petal.UpdateAnalogControls();
    petal.DebounceControls();
    led1.Update();
    led2.Update();

    freqParam.Init(petal.knob[Terrarium::KNOB_6], 4, 100.f, Parameter::LINEAR);

    osc_trem.SetWaveform(osc_trem.WAVE_SIN);
    osc_trem.SetFreq(freqParam.Process());
    osc_trem.SetAmp(1.f);
    led2.Set(osc_trem.Process());
}
 
Last edited:
I am very new to this hardware and to dealing with hardware in general. I tried loading your rhythm_delay.bin onto my daisy in the terrarium with the Daisy Web Programmer. However, while I'm sure that it's loaded and the LEDs seem to go on and off functionally as far as I understand, nothing else seems to happen, on or off. No noise or anything besides the LEDs even with fiddling with switches and knobs. Do you or anyone have any advice on where I went wrong or how I can get this working?
 
I am very new to this hardware and to dealing with hardware in general. I tried loading your rhythm_delay.bin onto my daisy in the terrarium with the Daisy Web Programmer. However, while I'm sure that it's loaded and the LEDs seem to go on and off functionally as far as I understand, nothing else seems to happen, on or off. No noise or anything besides the LEDs even with fiddling with switches and knobs. Do you or anyone have any advice on where I went wrong or how I can get this working?
Is this the first program you are trying to run with your Daisy/Terrarium board? If it is, you may want to make sure the audio on the Terrarium board itself works first. You can do that by removing the Daisy chip and using a wire to connect where pins 16 and 18 of the Daisy would plug into (when looking at the component side of the Terrarium the bottom row of pins goes from 1 on the left to 20 on the right).

Another thing that took me a minute to realize was that the Terrarium pcb needs its own +9v power for the TL072 input/output buffers. So even if you are powering this Daisy board with the usb, you still have to hook up power to the Terrarium PCB for the buffers to pass audio.
 
Is this the first program you are trying to run with your Daisy/Terrarium board? If it is, you may want to make sure the audio on the Terrarium board itself works first. You can do that by removing the Daisy chip and using a wire to connect where pins 16 and 18 of the Daisy would plug into (when looking at the component side of the Terrarium the bottom row of pins goes from 1 on the left to 20 on the right).

Another thing that took me a minute to realize was that the Terrarium pcb needs its own +9v power for the TL072 input/output buffers. So even if you are powering this Daisy board with the usb, you still have to hook up power to the Terrarium PCB for the buffers to pass audio.
Thanks for the help! I went back through the connections on the Terrarium board and found the issue. How did you start writing code for the daisy? I began going through the tutorial for using the Arduino app, but I already encountered an issue. Do you use a C++ IDE and export as .bin?
 
@Roder I started with the multidelay @tcpoint set up to work with the Terrarium then modified it in C++. I have an IDE working now (Visual Studio Code), but I don't use it and just write/modify the C++ code from the terminal using emacs. You don't need an IDE to compile the programs, if there is a 'Makefile' in the directory you can just type 'make' from the command line and it compiles the .bin for you. To get things setup on my computer (MacOS) I just went through the DaisyWiki.
 
Which version of macOS are you on? I've yet to get a terrarium friendly .bin while compiling in macOS (the blink example does blink, but no macOS compiled bins pass audio or give working leds)
 
Which version of macOS are you on? I've yet to get a terrarium friendly .bin while compiling in macOS (the blink example does blink, but no macOS compiled bins pass audio or give working leds)
I am still using Mojave, but I don't think I had to do anything beyond what was in the DaisyWiki getting started documentation. There is another thread here about getting things set up on Big Sur which includes IDE stuff you don't need to get working to just be able to command line compile - but if the stuff in that is set up and working it will include compiling good .bin files.
 
Alright, spent some time on this and learned a few things. Most notably, Daisy supports C++14, but its API is written in a style akin to C++98 which is frustrating. I did rearrange the code anyway though.

First of all, I created a class to encapsulate each delay line, kinda like the struct you defined in your code, plus a couple useful methods:

C++:
// Encapsulate the DelayLine class with additional methods to properly set the
// feedback.
template<size_t max_size>
class SingleDelay {
public:
  void Init() {
    line_.Init();
  }

  float Process(float in, float delay_target, float feedback) {
    //set delay times
    fonepole(current_delay_, delay_target, .0002f); // This smoothes out the delay when you turn the delay control?
    line_.SetDelay(current_delay_);
    float read = line_.Read();
    line_.Write((feedback * read) + in);
    return read;
  }

private:
  DelayLine<float, max_size> line_;
  float current_delay_{};
};

Next, I made a class to encapsulate the controls. It's mostly boilerplate, which makes it perfect for encapsulation: the idea is to put all the junk in one drawer that you can write once, test that it works properly and never look at again. Close that drawer with all your take out menus, soy sauce packets and chopsticks and forget about it:

C++:
// This class takes care of all controls for the quadruple delay.
class Controls {
public:
  explicit Controls(DaisyPetal* petal) : petal_(petal) {}
  void Init(float sample_rate, size_t max_delay) {
    for (int i = 0; i < 4; ++i) {
      delay_on_[i] = true;
    }
    delay_param_.Init(petal_->knob[Terrarium::KNOB_1], sample_rate * .05, max_delay * 1.0, Parameter::LINEAR);
    mix_param_.Init(petal_->knob[Terrarium::KNOB_2], 0.0, 1.0, Parameter::LINEAR);  // mix is knob 2
    feedback_param_.Init(petal_->knob[Terrarium::KNOB_3], 0.0, /*maxFeedback*/ 1.0, Parameter::LINEAR);  // feedback is knob 3
   
    // Initialize the leds
    led1_.Init(petal_->seed.GetPin(Terrarium::LED_1),false);
    led2_.Init(petal_->seed.GetPin(Terrarium::LED_2),false);
  }

  void Process() {
    delay_target_ = delay_param_.Process();
    mix_ = mix_param_.Process();
    feedback_ = feedback_param_.Process();
    int switches[4] = {Terrarium::SWITCH_1, Terrarium::SWITCH_2, Terrarium::SWITCH_3, Terrarium::SWITCH_4};
    for(int i=0; i<4; ++i) {
      delay_on_[i] = petal_->switches[switches[i]].Pressed();
    }
    // ON/OFF footswitch
    if(petal_->switches[Terrarium::FOOTSWITCH_1].RisingEdge()) {
      pass_through_on_ = !pass_through_on_;
      led1_.Set(pass_through_on_ ? 0.0f : 1.0f);
      // Above is an imbedded 'if' statement. If 'passThruOn=true' then set the led value to 0, if 'passThruOn=false' then set led value to 1.0
    }
  }

  float delay() const { return delay_target_; }
  float feedback() const { return feedback_; }
  bool delayOn(int i) const { return delay_on_[i]; }
  bool passThroughOn() const { return pass_through_on_; }
  float mix() const { return mix_; }

private:
  DaisyPetal* petal_;
  Led led1_, led2_;
  Parameter feedback_param_, mix_param_, delay_param_;
  bool pass_through_on_ = true;
  bool delay_on_[4];
  float delay_target_{}, feedback_{}, mix_{};
};

Next, I declared some static variables needed by the callback. This is mostly a work around for the limitation in the Daisy API I mentioned above. In C++14, you would use a lambda rather than a function pointer, which can capture local variables. Function pointers cannot, so they require static variables. I can explain more in detail if you are interested, but here's the code:

C++:
// No way to get away from this, see below.
static const int MAX_DELAY = 48000 * 3;
// These static pointers are just here to be captured by the callback below. They are set in the main function.
static Controls* scontrols;
static CrossFade* scfade;
static SingleDelay<MAX_DELAY>* sdelays;

void AudioCallback(float** in, float** out, size_t size) {
  // Process all controls.
  scontrols->Process();
  // Set the crossfade.
  scfade->SetPos(scontrols->mix());
  // Process each sample
  for (int sample = 0; sample < size; ++sample) {
    if (scontrols->passThroughOn()) {
      out[0][sample] = in[0][sample];
    } else {
      float all_delay_signals = 0;
      for (int i = 0; i < 4; ++ i) {
        if (scontrols->delayOn(i)) {
          // Each delay line is shifted by 1/4 the delay time compare to the previous one.
          // This is the same as the original code, just written slightly differently.
          all_delay_signals += sdelays[i].Process(in[0][sample], 0.25 * (i + 1) * scontrols->delay(), scontrols->feedback());
        }
      }
      // Use a crossfade object to maintain a constant power while mixing the delayed/raw audio mix
      out[0][sample] = scfade->Process(in[0][sample], all_delay_signals); // this sends 'final_mix' to the (left) output
    }
  }
}

To be honest, the Controls, CrossFade and SingleDelay<> objects could be declared static (rather than declaring static pointers) but I tend to never declare static objects if I can help it, because it can lead to undefined behavior in some contexts, although probably not here.

Finally, the main:

C++:
int main() {
  // Declare system and control objects and variables.
  DaisyPetal petal;
  petal.Init(); // Initialize hardware (daisy petal, and petal.seed)
  const float samplerate = petal.AudioSampleRate();
  const float max_delay_time_sec = 3.f;
  // Max delay sample length is the product of the rate and the max delay time defined above
  // This is how we would want to set the delay line size:
  // const size_t max_delay = static_cast<size_t>(samplerate * max_delay_time_sec);
  // However, since the class DelayLine uses statically allocated memory (an array), the size of the array must be
  // a compile time constant, which we cannot get at this time because AudioSampleRate is not a compile time constant.
  SingleDelay<MAX_DELAY> delays[4];
  for (int i = 0; i < 4; ++i) {
    delays[i].Init();
  }

  Controls controls(&petal);
  controls.Init(samplerate, MAX_DELAY);

  CrossFade cfade; //this is for a CrossFade object, which will blend the wet/dry and maintain a constant mixed volume
  // set params for CrossFade object
  cfade.Init();
  cfade.SetCurve(CROSSFADE_CPOW);
  // This sets to crossfade to maintain constant power, which will maintain a constant volume as we go from full dry to full wet on the mix control

  // Set the static pointers to point to the variables declared above
  scontrols = &controls;
  scfade = &cfade;
  sdelays = delays;

  petal.StartAdc();
  petal.StartAudio(AudioCallback);

  while (true) {
    dsy_system_delay(6);
  }
}
 
Back
Top