recording a loop while granular freezing

Krischomat

Well-known member
I started a sampler/looper project and right now I can record an audio loop and I setted up two grain object to freeze parts of the loop. The thing is now, when I have the freezing going on and I want to record a new loop, the whole audio stops for the recording. When I stop the recording the freeze is there as before the recording. I tried my luck with adding AudioNoInterrupts() at different points in the void recording() and void continuerecording() part but this did not help and dissabled the recording option.

Here is the Code:

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

// GUItool: begin automatically generated code
AudioInputI2S              in;           //xy=105,63
AudioEffectFade            fade1;
AudioEffectFade            fade2;
AudioEffectGranular        granular1;
AudioEffectGranular        granular2;
AudioEffectGranular        granular3;
AudioEffectFreeverbStereo  freeverbs1;
AudioEffectEnvelope        envelope1;
AudioEffectMultiply        multiply1;


AudioSynthWaveform         waveform1;
AudioSynthWaveformDc       dc1;

AudioMixer4                mixer1;
AudioMixer4                mixerL;
AudioMixer4                mixerR;
AudioMixer4                revSend1;
AudioMixer4                granularMixL;
AudioMixer4                granularMixR;
AudioMixer4                mixer2grain2;
AudioMixer4                mixer2grain3;
AudioMixer4                mixerDc;


AudioAnalyzePeak           peak1;          //xy=278,108
AudioRecordQueue           queue1;         //xy=281,63
AudioPlaySdRaw             playRaw1;       //xy=302,157
AudioOutputI2S             out;           //xy=470,120
AudioConnection            patchCord1(in, 0, queue1, 0);
AudioConnection            patchCord2(in, 0, peak1, 0);
AudioConnection            patchCord3(playRaw1, 0, fade1, 0);
AudioConnection            patchCord4(playRaw1, 0, fade2, 0);
AudioConnection            patchCord5(fade1, 0, mixer1, 0);
AudioConnection            patchCord6(fade2, 0, mixer1, 1);
AudioConnection            patchCord7(mixer1, 0, granular1, 0);
AudioConnection            patchCord8(granular1, 0, envelope1, 0);
AudioConnection            patchCord9(envelope1, 0, revSend1, 0);
AudioConnection            patchCord10(revSend1, 0, freeverbs1, 0);
AudioConnection            patchCord11(granular1, 0, mixerL, 0);
AudioConnection            patchCord12(granular1, 0, mixerR, 0);
AudioConnection            patchCord13(freeverbs1, 0, mixerL, 1);
AudioConnection            patchCord14(freeverbs1, 1, mixerR, 1);
AudioConnection            patchCord15(mixerL, 0, out, 0);
AudioConnection            patchCord16(mixerR, 0, out, 1);

// Granular Mix

AudioConnection            patchCord17(granular1, 0, mixer2grain2, 0);
AudioConnection            patchCord18(granular1, 0, mixer2grain3, 0);
AudioConnection            patchCord19(mixer2grain2, 0, granular2, 0);
AudioConnection            patchCord20(mixer2grain3, 0, granular3, 0);
AudioConnection            patchCord21(granular2, 0, granularMixL, 0);
AudioConnection            patchCord22(granular2, 0, granularMixR, 0);
AudioConnection            patchCord23(multiply1, 0, granularMixL, 1);
AudioConnection            patchCord24(multiply1, 0, granularMixR, 1);
AudioConnection            patchCord25(granularMixL, 0, mixerL, 2);
AudioConnection            patchCord26(granularMixR, 0, mixerR, 2);

// VCA

AudioConnection            patchCord27(granular3, 0, multiply1, 0);
AudioConnection            patchCord28(waveform1, 0, mixerDc, 0);
AudioConnection            patchCord29(dc1, 0, mixerDc, 1);
AudioConnection            patchCord30(mixerDc, 0, multiply1, 1);

AudioControlSGTL5000       sgtl5000_1;     //xy=265,212
// GUItool: end automatically generated code

// For a stereo recording version, see this forum thread:
// https://forum.pjrc.com/threads/46150?p=158388&viewfull=1#post158388

// A much more advanced sound recording and data logging project:
// https://github.com/WMXZ-EU/microSoundRecorder
// https://github.com/WMXZ-EU/microSoundRecorder/wiki/Hardware-setup
// https://forum.pjrc.com/threads/52175?p=185386&viewfull=1#post185386

// Bounce objects to easily and reliably read the buttons
Bounce buttonRecord = Bounce(0, 8);
Bounce buttonGrain =  Bounce(1, 8);  // 8 = 8 ms debounce time
Bounce buttonPlay =   Bounce(2, 8);
Bounce buttonGrain2 =   Bounce(3, 8);
Bounce buttonGrain3 =   Bounce(28, 8);
Bounce changeWaveform = Bounce(29, 15);

#define GRANULAR_MEMORY_SIZE 10000  // enough for 290 ms at 44.1 kHz
int16_t granularMemory[GRANULAR_MEMORY_SIZE];

#define GRANULAR_MEMORY_SIZE2 10000  // enough for 290 ms at 44.1 kHz
int16_t granularMemory2[GRANULAR_MEMORY_SIZE2];

#define GRANULAR_MEMORY_SIZE3 10000  // enough for 290 ms at 44.1 kHz
int16_t granularMemory3[GRANULAR_MEMORY_SIZE3];


// which input on the audio shield will be used?
const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;


// Use these with the Teensy Audio Shield
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14


// Remember which mode we're doing
int mode = 0;  // 0=stopped, 1=recording, 2=playing
int mode1 = 0;
int mode2 = 0;  // 0=stopped, 1=recording, 2=playing

int mode12 = 0;
// The file where data is recorded
File frec;

bool State = false;
bool Stateenv = false;
int playmode = 0;
int playmode2 = 0;
unsigned long previousMillis = 0;
long interval = 500;
long interval1 = interval - 50;
long intervalenv = 500;

int current_waveform = 0;

void setup() {

  granular1.begin(granularMemory, GRANULAR_MEMORY_SIZE);
  granular2.begin(granularMemory2, GRANULAR_MEMORY_SIZE2);
  granular3.begin(granularMemory3, GRANULAR_MEMORY_SIZE3);

  mixer1.gain(0, 0.8);
  mixer1.gain(1, 0.8);
  mixer1.gain(2, 0.8);
  mixerL.gain(0, 0.6);
  mixerR.gain(0, 0.6);
  mixerL.gain(1, 0.6);
  mixerR.gain(1, 0.6);
  mixerL.gain(2, 0.6);
  mixerR.gain(2, 0.6);
  mixer2grain2.gain(0, 0);
  mixer2grain3.gain(0, 0);
  granularMixL.gain(0, 0.7);
  granularMixR.gain(0, 0.3);
  granularMixL.gain(1, 0.3);
  granularMixR.gain(1, 0.7);
  mixerDc.gain(0, 1);
  mixerDc.gain(1, 1);


  // Configure the pushbutton pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(28, INPUT_PULLUP);
  pinMode(29, INPUT_PULLUP);

  // Audio connections require memory, and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(240);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

  // Initialize the SD card
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // stop here if no SD card, but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }

  current_waveform = WAVEFORM_SINE;
  waveform1.begin(current_waveform);
}



void loop() {

  /// Looplength

  unsigned long currentMillis = millis();
  interval = map (analogRead(A13), 0, 1023, 50, playRaw1.lengthMillis());
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

    if (State == false) {
      State = true;
    } else {
      State = false;
    }
  }

  if (currentMillis - previousMillis >= interval1) {

        if (Stateenv == false) {
          Stateenv = true;
        } else {
          Stateenv = false;
        }
  }

  env1();
  env2();
  grain();
  grain2();
  grain3();
  reverb();
  envelope();
  vca1();

  // First, read the buttons
  buttonRecord.update();
  buttonPlay.update();


///////////////// 1st Sample

  // Respond to button presses
  if (buttonRecord.fallingEdge()) {
    
    stopPlaying1();
    stopPlaying2();
    startRecording();
    playmode = 0;
    
  }

  if (buttonRecord.risingEdge()) {
     
    stopRecording();
    playmode = 1;
  }


  if (playmode == 1) {

    //play1

    if (State == true) {
      
      if (mode == 0) {
        startPlaying1();
      }
    }

    if (State == false) {
      stopPlaying1();
    }

    //play2
    if (State == false) {

      if (mode1 == 0) {
        startPlaying2();
      }
    }

    if (State == true) {
      stopPlaying2();
    }
  }



  // If we're playing or recording, carry on...
  if (mode == 1) {
 
    continueRecording();
  }
  if (mode == 2) {
    continuePlaying1();

  }
  if (mode1 == 2) {
    continuePlaying2();
  }


}

////////////////// 1st Sample

void startRecording() {
  Serial.println("startRecording");
  
  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
  }
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  if (frec) {
   
    queue1.begin();
    mode = 1;
  }
}

void continueRecording() {
  
  if (queue1.available() >= 2) {
    
    byte buffer[512];
    
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    memcpy(buffer + 256, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    // write all 512 bytes to the SD card
    //elapsedMicros usec = 0;
    frec.write(buffer, 512);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);


  }
}

void stopRecording() {
  
  Serial.println("stopRecording");
  AudioInterrupts();
  queue1.end();
  if (mode == 1) {
    while (queue1.available() > 0) {
      frec.write((byte*)queue1.readBuffer(), 256);
      queue1.freeBuffer();
    }
    frec.close();
  }
  mode = 0;
}

//playing1
void startPlaying1() {

  playRaw1.play("RECORD.RAW");
  mode = 2;
}

void continuePlaying1() {
  if (!playRaw1.isPlaying()) {
    playRaw1.stop();
    mode = 0;
  }
}

void stopPlaying1() {
 

  if (mode == 2) playRaw1.stop();
  mode = 0;
}

//playing2
void startPlaying2() {

  playRaw1.play("RECORD.RAW");
  mode1 = 2;
}

void continuePlaying2() {
  if (!playRaw1.isPlaying()) {
    playRaw1.stop();
    mode1 = 0;
  }
}

void stopPlaying2() {

  if (mode1 == 2) playRaw1.stop();
  mode1 = 0;
}


////////////// Fades

void env1() {
  if (Stateenv == false) {

    fade1.fadeOut(50);
  } else {

    fade1.fadeIn(50);
  }

}

void env2() {
  if (Stateenv == true) {

    fade2.fadeOut(50);
  } else {

    fade2.fadeIn(50);
  }

}

/////////// grain for main loop
void grain() {
  buttonGrain.update();

 // float knobA12 = (float)analogRead(A12) / 1023.0;
  float knobA11 = (float)analogRead(A11) / 1023.0;

  // Button 1 starts Pitch Shift effect
  if (buttonGrain.risingEdge()) {
   
   
  //  float msec = 10.0 + (knobA12 * 100.0);
    granular1.beginPitchShift(1000); 
  }

   // Continuously adjust the speed, based on the A3 pot
  float ratio;
  //ratio = powf(2.0, knobA11 * 2.0 - 1.0); // 0.5 to 2.0
   ratio = powf(2.0, knobA11 * 6.0 - 3.0); // 0.125 to 8.0 -- uncomment for far too much range!
  granular1.setSpeed(ratio);

}

/////grain freeze 1
void grain2() { 
    buttonGrain2.update();
            
  float knobA14 = (float)analogRead(A14) / 1023.0;
  float knobA10 = (float)analogRead(A10) / 1023.0;
    
  // Button 0 starts Freeze effect
  if (buttonGrain2.fallingEdge()) {
    mixer2grain2.gain(0, 1);
    float msec2 = 50.0 + (knobA14 * 150.0);
    granular2.beginFreeze(msec2);
    Serial.print("Begin granular freeze using ");
    Serial.print(msec2);
    Serial.println(" grains");
  }
  if (buttonGrain2.risingEdge()) {
    mixer2grain2.gain(0, 0);
    granular2.stop();
  }

  float ratio2;
  //ratio2 = powf(2.0, knobA10 * 2.0 - 1.0); // 0.5 to 2.0
  ratio2 = powf(2.0, knobA10 * 6.0 - 3.0); // 0.125 to 8.0 -- uncomment for far too much range!
  granular2.setSpeed(ratio2);

}

/////grainfreeze2
void grain3() { 
    buttonGrain3.update();
            
  float knobA14 = (float)analogRead(A14) / 1023.0;
  float knobA10 = (float)analogRead(A10) / 1023.0;
    
  // Button 0 starts Freeze effect
  if (buttonGrain3.fallingEdge()) {
    mixer2grain3.gain(0, 1);
    float msec3 = 50.0 + (knobA14 * 130.0);
    granular3.beginFreeze(msec3);
    Serial.print("Begin granular freeze using ");
    Serial.print(msec3);
    Serial.println(" grains");
  }
  if (buttonGrain3.risingEdge()) {
    mixer2grain3.gain(0, 0);
    granular3.stop();
  }
  
  float ratio3;
  //ratio2 = powf(2.0, knobA10 * 2.0 - 1.0); // 0.5 to 2.0
  ratio3 = powf(2.0, knobA10 * 6.0 - 3.0); // 0.125 to 8.0 -- uncomment for far too much range!
  granular3.setSpeed(ratio3);

}
             
        

void reverb() {

  //float KnobA10 = (float)analogRead(A10) / 1023.0;

  freeverbs1.roomsize(0.8);
  freeverbs1.damping(1);

  //revSend1.gain(0, KnobA10);
  //revSend1.gain(1, KnobA10);
  //revSend1.gain(2, KnobA10);

}

void envelope() {

  if (buttonPlay.fallingEdge()) {
    envelope1.noteOn();
  }

  if (buttonPlay.risingEdge()) {
    envelope1.noteOff();
  }

  envelope1.attack(50);
  envelope1.hold(10000);
  envelope1.release(11000);
  envelope1.sustain(1);

}

void vca1(){

  float fq1 = analogRead(A14) / 20;
  changeWaveform.update();
   
  waveform1.amplitude(1);
  waveform1.frequency(fq1 + 0.1);
  dc1.amplitude(1);
  

  if (changeWaveform.fallingEdge()){
    switch (current_waveform) {
      case WAVEFORM_SINE:
        current_waveform = WAVEFORM_SQUARE;
        Serial.println("Square");
        break;
      case WAVEFORM_SQUARE:
        current_waveform = WAVEFORM_SAMPLE_HOLD;
        Serial.println("S&H");
        break;
      case WAVEFORM_SAMPLE_HOLD:
        current_waveform = WAVEFORM_SINE;
        Serial.println("Sine");
        break;
    }
    AudioNoInterrupts();
    waveform1.begin(current_waveform);
    AudioInterrupts();
  }

}
 
I'm not sure if this is helpful, but if you save the project as a new file name it will re-compile everything, that seems to have helped me from time to time.

There seems to be almost the same effect of causing a complete re-compile by changing the cpu speed as well.

Also, you may want to try upping the amount of AudioMemory.

Good luck!
 
Thanks for your replys! I could not solve the problem yet... I just added a blink without delay code to my program to check if the LED keeps blinking while I am recording a new loop. So this is working actually. The only thing that stopps is the granular freeze. Must be something with the memory I guess that I don´t understand yet. I also tried to remove the stopPlaying() from the startRecording(). With the effect that it actually still plays a short part of the loop that I then overwrite with the recording. Although a bit glichty... I guess it is what still is in the memory. So I need to tell the teensy somehow that it still should read the granular freezing from the memory while I am recording or so? I think the granular object is recording into the memory?
 
Try and see what happens if you disable your analogRead on A13. I think A13 is on ADC1 on the Teensy 4.1 and audio also uses ADC1 for its input. And its know that this leads to problems.
But I have not tried your setup myself.

Cor
 
Hmm the A13 on the teensy 4.1 is on a pin that is not connected to the Audio Shield. In the beginning I had a problem using Digital Out 13 which is also connected to the onboard LED (I guess). But at the moment all my Pots are going in to inputs that are not used by the Audio Shield.
 
This ADC problem between Analogread and audio isnt related to a specific pin according to me, it has to do with ADC1 (and for ealier teensies ADC0). If you use that ADC also for analogread (so any of the ADC1 pins) this gives issues. Thats why I send you earlier a link ...
 
I suggested to test without any analogread, if it then works you know analogread is part of the problem. If it doesnt play it cant be due to analogread. If it works, you need to select an ADC from ADC2 on the teensy, the link above shows how to find those.
 
Okay will try that out. Also just added a reverb after everything and you can still hear the reverb fading out when everything stops while I am pressing the record button. But lets see what happens without any Analog Ins...
 
Thats unfortunate, I have no idea what breaks your code. You could try and add serial.print commands to see where in the flow it stops.
 
I did a little bit more testing and used a delay object after the granular freezing. And when I record a new loop and the granular freezing stops, I can still hear the delay fadeing out. So it seems that the recording is stopping from reading the grain buffer but it still reads the delay buffer. So what can I do with this information? I can make a weird work around by opening the delay when the recording starts and set the delay time to the grain size with a feedback? Hmm...
 
You could see what AudioMemoryUsageMax() tells you on the reserved audiomemory. You have reserved 240 blocks I see but use very large granular buffers, maybe some blocks do not get cleared properly. This simple metric can be helpfull.
 
So thats also not the issue ... most of the audio objects run behind the screens in interrupts so they are a bit tricky to debug.
 
Maybe it has to do with the structure of the granular object? Just wondering if instead of using the granular object for freezing I could just write a little code that just records directly into the memory and repeats that memory?
 
I am wondering if this could matter. In that Granular Freezing part of the code here:

Code:
void grain2() {
  buttonGrain2.update();

  float FaderFreeze1Pitch = mux1.read(2) / 1023.0;
  //float FaderFreeze1Length = mux1.read(3) / 1023.0;

  // Button 0 starts Freeze effect
  if (buttonGrain2.fallingEdge()) {
    mixer2grain2.gain(0, 1);
 //   float msec2 = 50.0 + (FaderFreeze1Length * 150.0);
    float msec2 = 400;    
    granular2.beginFreeze(msec2);
    Serial.print("Begin granular freeze using ");
    Serial.print(msec2);
    Serial.println(" grains");
    Serial.println(millis());
  }
  if (buttonGrain2.risingEdge()) {
    mixer2grain2.gain(0, 0);
    granular2.stop();
  }

  float ratio2;
  //ratio2 = powf(2.0, knobA10 * 2.0 - 1.0); // 0.5 to 2.0
  ratio2 = powf(2.0, FaderFreeze1Pitch * 2.0 - 1.0); // 0.125 to 8.0 -- uncomment for far too much range!
  granular2.setSpeed(ratio2);

}

I added this
Code:
 Serial.println(millis());

The Idea was that I wanted to see if the ms are still printed to the Serial Monitor when I start to record a new Loop. I assumed to see that the monitor is printing the rising ms over and over again but instead I just get one number once the granular freezing button is on the falling edge. Could it be that there is a correlation between that and the stopping when the record button is on the falling edge?
 
But that serial.println(millis()) is inside a buttonstate it seems. You have 8ms debounce on those, maybe a bit short ?
 
Now that I put it to the beginning of the void grain2() it counts the ms also during the recording mode. And I tried with 3ms debounce but still... it stays mysterious to me. Maybe just a limit of the granular object.
 
Back
Top