Issues making Petal "Looper" Example work with Seed board (Arduino IDE)

Hi Everyone!

I’ll start off by saying I’m quite new to the Daisy platform (and DSP in general), but am very excited by the applications that it offers. I’ve dabbled with some of the examples for Seed, and taken some inspiration from posts here on the forum, but my latest endeavor is trying to make the “Looper” example for the Petal work on my Seed board, in a breadboard with I/O and power broken out. I’ve had success compiling and uploading my edited example code, but the functionality of the looper isn’t there… (I get audio, and my volume control seems to work, however). I’m sure I’m making some GLARING errors to more experienced programmers, so any and all advice/help would be greatly appreciated.

Many thanks!

#include "DaisyDuino.h"

#define MAX_SIZE (48000 * 60 * 5)  // 5 mins of floats at 48kHz

static DaisyHardware hw;

Switch button;

float volume;

bool first = true;  //first loop (sets length)
bool rec = false;   //currently recording
bool play = false;  //currently playing

int pos = 0;
float DSY_SDRAM_BSS buf[MAX_SIZE];
int mod = MAX_SIZE;
int len = 0;  //Length
float dry = 0;
float wet = 0;
bool res = false;

void ResetBuffer();

void Controls();

void NextSamples(float &output, float *in, size_t i);

static void AudioCallback(float *in, float *out, size_t size) {
  float output = 0;

  Controls();

  for (size_t i = 0; i < size; i ++) {
    NextSamples(output, in, i);

    output = output * volume;
    out[i] = out[i + 1] = output;
  }
}

void setup() {

  // INIT
  hw = DAISY.init(DAISY_PETAL, AUDIO_SR_48K);
  ResetBuffer();

  button.Init(1000, true, 28, 0);

  rec = false;
  play = false;

  DAISY.begin(AudioCallback);
}

void loop() {
}

// Resets the buffer
void ResetBuffer() {
  play = false;
  rec = false;
  first = true;
  pos = 0;
  len = 0;
  for (int i = 0; i < mod; i++) {
    buf[i] = 0;
  }
  mod = MAX_SIZE;
}

void UpdateButtons() {
  // switch1 pressed
  if (button.Pressed()) {
    if (first && rec) {
      first = false;
      mod = len;
      len = 0;

    }



    res = true;
    play = true;
    rec = !rec;

  }

  // switch1 held
  if (button.TimeHeldMs() >= 1000 && res) {
    ResetBuffer();
    res = false;
  }

  // switch1 pressed and not empty buffer
  if (button.Pressed() && !(!rec && first)) {
    play = !play;
    rec = false;
  }
}

void Controls() {
  button.Debounce();
  volume = (map(analogRead(A0), 0.0, 1023.0, 0.0, 100.0) / 100.00);
  UpdateButtons();
}

void WriteBuffer(float *in, size_t i) {
  buf[pos] = buf[pos] * 0.5 + in[i] * 0.5;
  if (first) {
    len++;
  }
}

void NextSamples(float &output, float *in, size_t i) {
  if (rec) {
    WriteBuffer(in, i);
  }

  output = buf[pos];

  // automatic looptime
  if (len >= MAX_SIZE) {
    first = false;
    mod = MAX_SIZE;
    len = 0;
  }

  if (play) {
    pos++;
    pos %= mod;
  }

  if (!rec) {
    output = output * 0.5 + in[i] * (1 - 0.5);
  }
}

Welcome Os_Brennan!
Getting the Looper example to work with the Seed is a great idea!!

In order to focus on the hardware first, one thing that you can do is not alter the code and connect the components (like the buttons) the same exact way as Petal. It’s worth noting that the Petal is the Seed plus a breakout board with all the hardware configured already. You can find the pins that you can connect to in this diagram.

Another thing that helps is using Serial.println() to make sure if the buttons are working. You can put Serial.println("Switch 1 Pressed!"); to double check that your switch is hooked up correctly.

For example:

if (petal.buttons[0].RisingEdge()) {
    if (first && rec) {
      first = false;
      mod = len;
      len = 0;
    }

    Serial.println("Switch 1 Pressed!");

    res = true;
    play = true;
    rec = !rec;
  }

As for the code that you have right now, please change
hw = DAISY.init(DAISY_PETAL, AUDIO_SR_48K);
to
hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);

Also, you’re only using one switch/button right now but this part of the code is for a second switch:

// switch2 pressed and not empty buffer
  if (petal.buttons[1].RisingEdge() && !(!rec && first)) {
    play = !play;
    rec = false;
  }

So if you have another button in your workbench, I suggest hooking it up and then edit the code. I think this is the main issue so hopefully you’ll have everything working once this is addressed.

Let me know if you have any other questions!

1 Like

Thanks very much Takumi!

I added the extra footswitch, as well as the drywet pot, uploaded the “Looper” example code, and it’s working perfectly! The only think I’ve noticed, is that the loops tend to get quieter over time as they repeat. I recall seeing in the Looper.h file, that there are a few different types of modes for the looper, could this be why? If so, I’m not quite sure how I’d call the function from that header file.

I’m very much enjoying leaning from dissecting the included examples, but is there any sort of Reference page for Daisy that I’m missing, similar to the one for Arduino here?

Thanks again!

  • Oliver
1 Like

Nice! Making progress :slight_smile:

I’m not sure if there’s looper.h for DaisyDuino. Do you remember where you saw this?

As for the issue with volume decreasing, I’m guessing that you left the recording on and it keeps recording silence?
It’s an sound-on-sound type looper according this post, so if you don’t toggle the recording off, it keeps adding silence on top of your loop which I’m guessing is what’s causing the decrease in volume.

We will be working on more documentations for DaisyDuino! We’ve been setting things up the past few months actually. In the meantime, analyzing the example codes is the way to go!

Thanks Takumi!

No, the loops seem to fade in volume even if I’ve stopped recording. The Looper.h file is here, and the “mode” code I’m referring to is in the first 40-ish lines. Having access to these different styles of looping would actually be really great, so any advice on how to call those would be great :slight_smile:

Thanks again!

  • Oliver

Hey Oliver!

Let me set up the hardware so that we can troubleshoot this together. I will keep you posted.
Thank you for the wait!

Edit: Ok, I think I know what happened. I will get back to you tomorrow morning (EST time).
In the meantime, could you share a schematic or a photo of your electronics? Thanks!

Hey Takumi!

So, I added some LEDs to the breadboard to reflect “play” and “rec” modes, and you were bang on the money, I didn’t realise I WAS adding silence into the audio buffer… :man_facepalming: Sorry about that!

With that issue solved, I would still be interested in sorting out how to call the different “modes” from Looper.h. After that, I should be able to fly solo.

Thanks so much for your help!

-Oliver

Hey Oliver!

Perfect!! Yes, I was gonna recommend adding an LED to double check that your switch is wired up correctly and such. Glad to hear that you troubleshot it before I was able to get back to you :slight_smile:

With regards to the “modes”, the looper algorithm is implemented in Looper.ino all on its own from scratch, so it is independent from the Looper.h file that you found.
This means that you would need to implement those different modes. Referencing that loop.h file could be a good move.

Let’s have a closer look at Looper.ino again. It is actually more like the Frippertronics Mode (“infinite looping recording with fixed decay on each loop. The module acts like tape-delay set up”) due to the * 0.5s (the “fixed decay” that is decreasing/decaying the amplitude of the loop) in the WriteBuffer function.
This also explains the loop becoming quieter. With the recording not toggled off plus not recording anything (silence), your loop was getting quieter and quieter every cycle.

If it was in the actual “Normal” mode, your loop would have stayed at the same volume even when the recording toggle was not turned off (though, you were able to troubleshoot your hardware so I guess it worked out :wink: ).
So, how would we go about adding a “Normal” mode to this looper algorithm?
“Normal” mode would just mean getting rid of both * 0.5 in the WriteBuffer function.

But, it would be nice to switch back and forth between the two modes, right?

With all of these in mind, let’s have a look at this code snippet that was just put together.

bool normal_mode; // to keep it simple, false = frippertronics, and true is "Normal mode"

void WriteBuffer(float *in, size_t i) {
  float gain = 0.5; 

  if(normal_mode) //if in normal mode (normal_mode is true)
  {
    gain = 1.0;
  }

  buf[pos] = buf[pos] * gain + in[i] * gain;
  if (first) {
    len++;
  }
}

By changing the normal_mode boolean variable, you can switch back and forth between the two different modes. “Normal” mode means the gain is 1.0 (so no changes in the amplitude) and “Frippertronics” mode means the gain is 0.5 (so decreasing the amplitude every loop).

Finally, to be able to manually switch between the two modes, you can connect another footswitch or button to your Daisy in order to toggle between the normal_mode variable. Bonus point if you add another LED for visual feedback!

Let me know if you have any other questions. Have fun!!

Thanks Takumi! That’ll be super useful to implement, and you laid it out super clearly :slight_smile:

As it stands, I’m currently pretty sure I have my head wrapped around the example code, but a few things are still tripping me up. First, is a snippet from the ResetBuffer function;

  for (int i = 0; i < mod; i++) {
    buf[i] = 0;
  }
  mod = MAX_SIZE;

Am I correct in assuming, that this array counter is here to assure that, if ResetBuffer is called, the audio buffer remains empty? Not crucial to what I’m doin right now, just trying to understand everything.

Next thing, is a few of the bools and ints, specifically “res”, “len”, and “pos”. I can kind of understand what they’re doing, but some more clarification would be excellent.

Many thanks!

-Oliver

Hey Oliver!

Yes, correct! The buf[i] = 0; in that for-loop is emptying the buffer.

It certainly would help to have more comments explaining what’s going on in this code. Honestly, I would love to do a video on it. That would probably be the best format to explain this as you’ll see in a few minutes…

I’ll do my best to summarize those three variables. Please follow along by opening Looper.ino here.

  • res is utilized at line 86. In order to reset/empty the loop buffer, you need to hold down switch 1 for more than 1 second AND res needs to be true. So you can think of res more as “Is there a filled-in buffer that can be emptied? True or false?” Since the buffer starts out as empty when you fire up your looper hardware, the res is initialized as false. So holding down switch 1 for more than 1 second won’t do anything since there’s no buffer to empty! When you press and quickly let go of switch 1 (line 73), then the buffer is filled in. And res becomes true at line 80 soon after. Now that it’s “true”, it means that there’s a buffer that you can empty!

  • len is “length of the buffer”. It’s initialized as 0 and it’s incrementing at line 110 when you’re recording the buffer for the first time. Once it becomes the same value as the MAX_SIZE, the condition at line 122 is met. One of the things that happens there is first is now false which (I think) needs to be that in order for the playback condition to be met at line 92. Kind of a similar situation as to res. There needs to be a filled-in buffer to playback!

  • pos is “buffer position”. In order to playback the recorded buffer (which happens with output = buf[pos]; at line 119. That output is what we hear!), that integer variable needs to increment.
    That happens from line 128. There’s pos++ at line 129. And there’s also pos %= mod; happening at line 130. %= is a modulo calculation, which I’ll explain more shortly.
    Ok, so why not increment just with pos ++;? Let’s just assume that MAX_SIZE equals 48000 (so a 1 second loop since the sample rate is 48000 samples per second) and we also got rid of pos %= mod. As the pos increments and the loop playback reaches 1 second (which is when pos is 48000), the pos is going to increment to 48001!!! This is above the MAX_SIZE! We can’t exceed it. And there’s nothing in buf[48001] anyway.
    Now, here comes pos %= mod to the rescue! Note: mod is 48000 in this example (same as MAX_SIZE). When pos increments to 1, then pos %= mod will mean that pos will become the remainder of 1 divided by 48000. 1/48000 = 0 so the remainder is 1. Therefore, the pos is (once again) 1, cool. Then, the pos is incremented by 1 so it’s 2 now. 2/48000 is 0 again but the remainder is 2 this time.
    Ok? So how is this different from pos++??? Well, let’s jump forward to when the pos is incremented to 48000. 48000/48000 is equal to 1 with no remainder (equivalent to 0). SO, pos %= mod where pos is 48000 will result in pos becoming 0 once again. So!! That single line of code allows us to efficiently loop during playback!! That’s the power of modulo :muscle:

These are sort of thought processes that you can go through as you further analyze this code. It’s a pretty involved one so take your time with it :slight_smile:

Best of luck!!

1 Like

Hey Takami!

First off, I just wanted to thank you for all the guidance you’ve given. Over the last few weeks, I’ve been digging into this sketch (as well as the example files), and have managed to make some progress on my project!

My next hurdle with the looper, is to create a condition that allows for only the last recorded loop to be deleted, as opposed to clearing the audio buffer as in ResetBuffer();. My initial thoughts on this are to either;

  • Create a temporary buffer array to record to when rec = true;
    or
  • Utilise the Right audio buffer as a “record buffer”, the Left as the “loop buffer”, and print right to left as progressive loops are being stacked up (with the ability to separately clear the right and left buffers).

I’m not expecting you to hold my hand through the entire process (as you’ve been so incredibly helpful so far), but what would you do in this scenario? Is one or both of these ideas possible?

I do intend for this to be a stereo looper (or at least, stereo in, mono out) in the end, but if we go for the latter option, I could always add an additional codec, as with the Patch, if I’m not mistaken?

Thanks again so much!

-Oliver

Hi Oliver!

Ah I see, so you want to be able to say “Hmmm, I didn’t like what I just recorded, I want to delete it and retry again.”?
So I think one approach is indeed having two separate buffers.
One of them more as a “temporary” storage for the “last recorded” layer/loop (or maybe it’s best to call it “newly recorded” or newBuf). And the other for the single buffer with multiple previously recorded layers/loops (maybe call it layerBuf?).
And once you’re happy with the newly recorded layer, you can add that to that second buffer as you clear the newBuf (maybe during the next recording of newBuf. Perhaps it’s possible to override what’s in there already with the newly recorded sound!). And whenever you’re unsatisfied with the recording, you can delete/clear/reset that newly recorded layer and not add it onto the layers. This would be an amazing feature to have :slight_smile:

It should be possible to do all of this, so I suggest that you take your time, outline using a whiteboard or pencil + paper, and go step by step.
Think first about how the “newly recorded” buffer is recorded (similar to how it already is though), how that “newly recorded” buffer will be “recorded” onto the “layer” buffer, and etc etc. Once the recording parts are working, I suggest you next think about how to go about deleting just the “newly recorded” buffer whenever you’re unsatisfied with the recording. You may need something like ResetNewBuffer().

Yes, but it’ll be pretty tricky as there’s no documentation on how to do this yet.

Good luck!