Audio Looping investigations

Status
Not open for further replies.

propa

Well-known member
Hello,

I'd like to discuss looping, I've found three ways so far, forum user Moo, Cutlasses and Bleep labs have all posted code to that will allow looping and control of a sampled sound to some degree, each have their own drawbacks and advantages.

Here's what I've found so far..

if (Teensy >= 3.5) {Moo’s code, Cutlasses code}

+'s Moo's reads from SD, and has a large range and reverse, similarly Cutlass has reverse, and file scanning(changing placement of playback head), and loop end shortening. Very cool. -'s Moo's only reads from SD, and only in RAW, requires 3.5 or 3.6 to perform well, SPI SD reading from audio shield is slow compared to dedicated SD on 3.5/3.6, that doesn't block any other pins. Cutlasses code I haven't tested and can't compile due to an error with this
Code:
 void          update( ADC& adc );
line, but the video evidence is convincingly cool. If anyone can figure out what I'm doing wrong, or can get it to compile themselves I'd be interested to hear how you did it.

Here's the link to Cutlasses code: https://github.com/cutlasses/AudioFreezeV2

and here's the link to moo's code: https://github.com/newdigate/Audio/tree/play-audio-sd-raw-resampled

if (Teensy <= 3.2 && (u == qwik || durty)) {Bleep’s Tape Delay}
Cheap, quick looper using Tape delay to grab a loop of feeding back delay as a simple answer to an over-dubbing looper. To install download bleep's fork of audio, take out TapeDelayEffect.h and TapeDelayEffect.cpp and put into your current audio library, also change audio.h to include a reference to you newly added audio effect.

This code creates a simple overdubbing looper controlled based around a delay line, when a switch is depressed it stares into the void of infinity waiting for a cheap buzz, I mean, when you let go of the button it stops looping.

Code:
/*
   Copyright (c) 2018 John-Michael Reed
   bleeplabs.com

   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 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.


   This is a looper based on the Tape Delay effect by Bleep Labs

*/

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=104,218
AudioMixer4              mixer1;         //xy=315,240
AudioEffectTapeDelay         tapeDelay1;         //xy=473,232
AudioOutputI2S           i2s2;           //xy=701.4285774230957,192.8571548461914

AudioConnection          patchCord1(i2s1, 0, mixer1, 0);
AudioConnection          patchCord2(mixer1, tapeDelay1);

AudioConnection          patchCord3(tapeDelay1, 0, i2s2, 0);
AudioConnection          patchCord4(tapeDelay1, 0, i2s2, 1);

AudioConnection          patchCord5(tapeDelay1, 0, mixer1, 1);

AudioControlSGTL5000     sgtl5000_1;     //xy=725.9999961853027,277.4285707473755
// GUItool: end automatically generated code

Bounce button1 = Bounce(6, 15);
Bounce button2 = Bounce(7, 15);

Bounce inputSelecta = Bounce(1, 15);
Bounce modeSelecta = Bounce(2, 15);

int led1 = 3;
int led2 = 4;

#define pot1_pin A1
#define pot2_pin A7

#define DELAY_MAX_LEN 25000
short tape_delay_bank[DELAY_MAX_LEN] = {};

float delay_time;
float feedback_level;


bool micOn;
bool inLoopMode;

bool loopRecButton;
bool deleteButton;

uint32_t cm, prev[4];

void setup() {
  
  AudioNoInterrupts();
  AudioMemory(10);
  pinMode(0, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  sgtl5000_1.inputSelect(AUDIO_INPUT_MIC);
  sgtl5000_1.micGain(36);
  tapeDelay1.begin(tape_delay_bank, DELAY_MAX_LEN, 5000, 0, 0); //bank to use, size of bank, delay time in samples , rate reduction, interpolation time
  //rate reduction of 0 means the delay will increment at the same rate as playback
  //at 1 we double out delay time by halving the sample rate. 2 quadrules it and so on.   
  //Halving the sample rate is nothing to be scared of!
 
  //interpolation time is how ling it takes for the "read head" to get to the desired dlay length. 
  //0 is as quickly as possible, 10 is a standard tape delay sound and 20 is getting a little slow and crazy
  mixer1.gain(0, .5);
  mixer1.gain(1, .5); //feedback level
  AudioInterrupts();
  analogReadAveraging(41);
}

void loop() {
  button1.update();
  button2.update();
  
  inputSelecta.update();
  modeSelecta.update();
  // read knobs, scale to 0-1.0 numbers
  float knobA2 = (float)analogRead(A1);
  float knobA3 = (float)analogRead(A7) / 64.0;
  float knobA4 = (float)analogRead(A6);

  sine1.frequency(knobA4);

  if (inputSelecta.fallingEdge()) 
  { 
    digitalWrite(led1,HIGH);
    sgtl5000_1.inputSelect(AUDIO_INPUT_MIC);
    Serial.println("MIC IN SELECTED");
    micOn = true;
  }

  if (inputSelecta.risingEdge()) 
  {
    digitalWrite(led1,LOW);
    sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
    Serial.println("LINE IN SELECTED");
    micOn = false;
  }

  if (modeSelecta.fallingEdge()) 
  { 
    Serial.println("Normal Delay Mode");
    mixer1.gain(0, 0.5);
    mixer1.gain(1, 0.5);
    digitalWrite(led2,HIGH);
    inLoopMode = true;
  }

  if (modeSelecta.risingEdge()) 
  {
    Serial.println("Simple Looping Mode");
    digitalWrite(led2,LOW);
    inLoopMode = false;
  }
 
  cm = millis();

  if (button1.fallingEdge()) 
  {
    loopRecButton = true;
  }
  
  if (button1.risingEdge()) 
  {
    loopRecButton = false;
  }

  if (button2.fallingEdge()) 
  {
    
    deleteButton = true;
  }
  
  if (button2.risingEdge()) 
  {
    deleteButton = false;
  }

  if (deleteButton)
  {
    sgtl5000_1.muteLineout();
    mixer1.gain(1, 0.0); // Would be better if it faded instead of clicking at the end
    
    //Serial.println("deleteButton");
  }

  if (!deleteButton)
  {
    sgtl5000_1.unmuteLineout();
  }

  if (!inLoopMode)
  { // This is normal delay mode, will feedback, feedback control seems to behave a bit weirdly
    if (cm - prev[1] > 5) { //to redusce nosie ist's best not to do this too rapidly and to smooth the pot reading.
    prev[1] = cm;
    delay_time = analogRead(A1) * ( DELAY_MAX_LEN / 1023.00);

    if (loopRecButton && !deleteButton)
    { 
      mixer1.gain(0, 0.5);
      feedback_level = 1.0;
    }
    if (!loopRecButton)
    { 
    mixer1.gain(0, 0.0);
    }
    
    mixer1.gain(1, feedback_level);
    tapeDelay1.length(delay_time);
  }
  }

  if (inLoopMode)
  {
    if (cm - prev[1] > 5) 
    { //to redusce nosie ist's best not to do this too rapidly and to smooth the pot reading.
    prev[1] = cm;
    delay_time = analogRead(pot1_pin) * ( DELAY_MAX_LEN / 1023.00);
    feedback_level = analogRead(pot2_pin) /1023;
    //mixer1.gain(0, 0.5);
    mixer1.gain(1, feedback_level);
    tapeDelay1.length(delay_time);
  }
  }

  if (cm - prev[0] > 500) 
  {
    prev[0] = cm;
//
//    Serial.print(AudioProcessorUsageMax());
//    Serial.print(" ");
//    Serial.println(AudioMemoryUsageMax());

//    Serial.println(delay_time);
//    Serial.println();

    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();
  }

}


Also if anyone would like to add their efforts in this investigation, please be my guest, I'd appreciate the contribution. The ideal outcome would be an object we can all use or a decent thread to point to when this question inevitably arrises again.

Points will be given, or gold stars, or rainbow coloured unicorn flakes.
 
In further investigations: https://github.com/cutlasses/AudioFreeze AudioFreeze v1 works without any compiler hiccups, but overflows the RAM on 3.2. I should be getting a 3.6 soon and will give it a test.

I have been able to get AudioFreezeV2 to compile if I take out all references to the interface code.

There are some head scratching differences, in v2 it calls "void update( ADC & adc );" in v1 it's just void update(), I don't understand what's going wrong with the "ADC& adc" part.

Code:
struct IO
{
  ADC                         adc;
  AudioInputAnalog            audio_input;
  AudioOutputAnalog           audio_output;

  IO() :
    adc(),
    audio_input(A0),
    audio_output()
  {
  }
};

Here's the whole project, if anyone can compile it please let me know what I'm missing: https://github.com/cutlasses/AudioFreezeV2

I think this ADC& adc is really the culprit, but i just can't figure out why.

Code:
class DIAL
{
  int           m_data_pin;

  int           m_current_value;
  
public:

  DIAL( int data_pin );

  bool          update( ADC& adc );
  float         value() const;  
};

I've watched the talk he's done at Electro Magnetic Field and says the code is open source, and says feel free to remix: http://www.cutlasses.co.uk/live/electromagnetic-field-festival/

I assume the code works and I'm doing something incorrect and dumb, or don't have the right library installed, if anyone has any insight ADC& adc problem I'm very keen to hear.
 
Hi,

interesting, thanks for sharing. I tried Cutlasses code as well, but I cannot get it to compile at all. I removed all the interface stuff but it still overflows in ram. region `RAM' overflowed by 190104 bytes

I had a look at the audiofreezeeffect.h but that does not have that much code at all. Tbh, I had a look for a good hour and as far as I can tell the audio interface stuff is only to make it easy to add buttons and whatnot to the device. I cannot seem to find where the actual audio manipulation bit of the 'new' code is. I mean, I could find e.g. how (and where it is defined) the reverse sample play is called, but I did not see where that piece of code actually sits. The video does look really cool, but as mentioned, once you strip out all the knobs and buttons from the files, there's not much left, is it perhaps incomplete?

EDIT: ok in a final attempts I noticed there was one ino file I did not look at, I was expecting the 'magic' to be in a .h or .cpp file... having another look now.
 
Hi,

interesting, thanks for sharing. I tried Cutlasses code as well, but I cannot get it to compile at all. I removed all the interface stuff but it still overflows in ram. region `RAM' overflowed by 190104 bytes

I had a look at the audiofreezeeffect.h but that does not have that much code at all. Tbh, I had a look for a good hour and as far as I can tell the audio interface stuff is only to make it easy to add buttons and whatnot to the device. I cannot seem to find where the actual audio manipulation bit of the 'new' code is. I mean, I could find e.g. how (and where it is defined) the reverse sample play is called, but I did not see where that piece of code actually sits. The video does look really cool, but as mentioned, once you strip out all the knobs and buttons from the files, there's not much left, is it perhaps incomplete?

EDIT: ok in a final attempts I noticed there was one ino file I did not look at, I was expecting the 'magic' to be in a .h or .cpp file... having another look now.

I got it to compile when targeting 3.6, you're right the interface parts are just for the knobs and buttons.

The magic is in this file: https://github.com/cutlasses/AudioFreeze/blob/master/AudioFreezeEffect.ino write to buffer, read buffer, write sample, read sample, set_freeze and set center all look like the actual audio functions. It's just another .ino he's included, would be silly if changing it to .cpp would fix everything.
 
OK, so both do not compile for me on the 3.2 - maybe I need to get a 3.6 to try these.

The other annoying find when using the 3.6 without the audio shield, is that the using the built in ADC through the analog pins completely knocks out use of analogRead, which is so gutting.

I thought about a work around but don't know if it's possible as adc AudioInputAnalog has no functions*, and that's to make sure you never polled analogread while feeding audio to adc, then when finished listening to adc (when looping has commenced) you go back to analog read.

*Maybe a function called .TurnOff() is needed. Ideally if adc is going to inhibit the function of reading analog read it would be great to have the ability to switch between either one in code. Please chime in if anyone thinks this would be a welcome addition, I think it's debilitating to loose all those precious pins!
 
I got Cutlasses code to compile on the 3.6 only to find once you go past a certain input volume it glitches the audio out for no reason and crashes the program, and not specified in the code either, as in this is a horrible unintentional glitch (at least you'd hope).

The effect is almost like an accidental grain loop, really annoying and frustrating, and I have no idea why someone would release broken code, or invite people at a hack to his github when it's in such a state. Good looking code, wish it sounded half as nice as the code looks.

on 3.6 with audio shield Moo's code just hangs and crashes the serial monitor, then the app, sometimes the computer.

So hopes of looping anything are slimming. The range on Bleep labs granular when using the 3.6 isn't as large as other forum users would have you beleive, I have it maxing before 1000ms, around 500/600ms, which is too small for most applications.

So if anyone has any other library's or code to explore, please hit me up. This is frustrating and boring as hell now, would appreciate any points in the right direction.

I tried extending the tape delay based one as well, but even with the 3.6 it just hasn't got enough memory. I can't seem to go beyond 32000 for some reason:

#define DELAY_MAX_LEN 32000
short tape_delay_bank[DELAY_MAX_LEN] = {};
 
on 3.6 with audio shield Moo's code just hangs and crashes the serial monitor, then the app, sometimes the computer.

I got a Teensy 3.6 finally and tried Moo's code as well. Got similar crashes (not of computer though) constantly until I set the mhz to 96 instead of 180 on the T3.6. Can you try that? The usage is pretty straight forwards, you set the playback rate, minus is reverse etc.

I did a fair bit of experimenting today and found that between -3.5 and +3.5 it worked stable but beyond that range it glitched out. Sometimes it would return to normal afterwards, sometimes not. I haven't tried Cutlasses code yet, will try that still.
 
Status
Not open for further replies.
Back
Top