Help needed for the Granular effect

Status
Not open for further replies.

Elekris

Member
I'm making an electronic drumset with piezo sensors and now I want to change the pitch of the instruments (kick, hi-hat, snare) using rotary encoders.
Everything works just fine. I have no compiler errors and my serial monitor shows the values I want to see. But (almost) nothing happens when I use the setSpeed() or beginPitchShift() methods of my granular effect objects.
I'm using the Teensy 3.5 with Audioshield. Sorry if my code is hard/annoying to read. This is my very first physical computing project and I'm also relatively new to programming as a whole.
I'll also include the whole project as a .zip file, if anyone wants to recreate it.

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

#include "AudioSampleKick.h"    // INDEX = 0
#include "AudioSampleHat.h"     // INDEX = 1
#include "AudioSampleSnare.h"   // INDEX = 2

AudioPlayMemory          sounds[4];     // This and other arrays have a length of 4, because initially 4 instruments were planned
AudioEffectGranular      granulars[4];  // Granular Effect for every sound
AudioMixer4              mix;
AudioOutputI2S           speakers;
AudioConnection          c1(sounds[0], granulars[0]);   // Sounds connect to their respective effect
AudioConnection          c2(sounds[1], granulars[1]);
AudioConnection          c3(sounds[2], granulars[2]);
AudioConnection          c4(granulars[0], 0, mix, 0);   // After the effects, the sounds get sent into the mixer
AudioConnection          c5(granulars[1], 0, mix, 1);
AudioConnection          c6(granulars[2], 0, mix, 2);
AudioConnection          c7(mix, 0, speakers, 0);       // Mixer output goes into both stereo channels
AudioConnection          c8(mix, 0, speakers, 1);

//AudioConnection          c1(sounds[0], 0, mix, 0);    An unsuccessful attempt to solve the problem: "Maybe the effect needs to be applied after the mixer just                                                                                                    
//AudioConnection          c2(sounds[1], 0, mix, 1);                                                   like in the example?"
//AudioConnection          c3(sounds[2], 0, mix, 2);
//AudioConnection          c4(mix, granulars[2]);
//AudioConnection          c5(granulars[2], 0, speakers, 0);
//AudioConnection          c6(granulars[2], 0, speakers, 1);

AudioControlSGTL5000     audioShield;

#define GRAN_MEM_SIZE 12800         // This is pretty much copied from the example. I have no idea how many integers I should use...
int16_t granMems[GRAN_MEM_SIZE][3];

Encoder volEnc(26, 27);   // Encoder which controls the sgtl5000 volume, see onVolChange()
int vol = 0;
float defaultVol = 0.5;
float gainMult[4] = {0.02, 0.025, 0.025};  // Volume multiplicator for every sound.

Encoder pitchEnc[3] = {Encoder(34, 35), Encoder(29, 28), Encoder(38, 39)};  // Pitch Encoders
int pitchVal[3] = {0, 0, 0};

int count = 3;    // Actual count of the instruments. Used for for loops.
int pins[4] = {32, 31, 33, 34};   // Pins used for the instruments. Last one is unused.
int tolerances[4] = {4, 5, 5, 4}; // Tolerance value for each piezo sensor. If a value is surpassed, a sound gets played.
int values[4];                    // Piezo sensor values stored each loop
int defaults[4];                  // For some reason the default value of my sensors is not always 0. That's why they are taken into account.
int durations[4] = {20000, 20000, 20000, 20000};    // Microsecond durations in which the peak of the piezo sensor gets recorded.
                                                    // The sound gets played when the duration is over.
int recordTimer[4]; // Used together with the durations
int recordHigh[4];  // Peak of the piezo sensor
bool isRecording[4] = {false, false, false, false}; // Boolean for each sensor. Recording can only start if the boolean is false.

void setup() {
  Serial.begin(4800);
  AudioMemory(100);
  audioShield.enable();
  audioShield.volume(defaultVol);

  for(int i = 0; i < count; i++){
    granulars[i].begin(granMems[i], GRAN_MEM_SIZE);
    pinMode(pins[i], INPUT);
    defaults[i] = analogRead(pins[i]);
    Serial.println("Piezo " + String(i) + " default value is " + String(defaults[i]));
  }
}

void loop() {
  if(vol != volEnc.read()) onVolChange();
  
  for(int i = 0; i < count; i++){
    int newPitch = round((float)pitchEnc[i].read() / 8.0);
    if(newPitch != pitchVal[i]) onPitchChange(i, newPitch);
    
    values[i] = analogRead(pins[i]);
    if(!isRecording[i] && values[i] > defaults[i] + tolerances[i]){
      isRecording[i] = true;
      recordHigh[i] = 0;
      recordTimer[i] = micros();
    }
    if(isRecording[i]){
      if(values[i] > recordHigh[i]){
        recordHigh[i] = values[i];
      }
      if(micros() >= recordTimer[i] + durations[i]){
        isRecording[i] = false;
        mix.gain(i, ((float)recordHigh[i] * gainMult[i]));
        switch(i){
          case 0:
            sounds[i].play(AudioSampleKick);
            break;
          case 1:
            sounds[i].play(AudioSampleHat);
            break;
          case 2:
            sounds[i].play(AudioSampleSnare);
            break;
        }
        Serial.println(   "Channel: " + String(i) + " Peak: " + String(recordHigh[i]) + " Volume: " + String(((float)recordHigh[i] * gainMult[i])) + " Length: "
                          + String(sounds[i].lengthMillis()));
      }
    }
  }
}

void onVolChange(){
  if(volEnc.read() > 50) volEnc.write(50);
  if(volEnc.read() < -50) volEnc.write(-50);
  vol = volEnc.read();
  float newVol = (defaultVol + ((float)vol / 100.0));
  audioShield.volume(newVol);
  Serial.println(newVol);
}

void onPitchChange(int enc, int newPitch){
  pitchVal[enc] = newPitch;
  pitchEnc[enc].write(newPitch * 8);
  float newSpeed = pow(2.0, ((float)newPitch / 12.0));
  granulars[enc].setSpeed(newSpeed);    
  //granulars[enc].beginPitchShift(newSpeed * 100.0); // I tried setSpeed() first, because I already knew the calculation for the ratio parameter.
  Serial.println("Encoder=" + String(enc) + " Value=" + String(pitchEnc[enc].read()) + " Pitch=" + String(newPitch) + " Speed=" + String(newSpeed));
}
 

Attachments

  • Drumset.zip
    48.3 KB · Views: 66
Last edited:
Also here's how the uncommented AudioConnections should look like:

Download.png
 
So now I also tested out the Granular effect example with a few songs that I played from a micro SD card and a potentiometer. The setSpeed() method has absolutely no effect. Does anyone have a clue?
 
The Granular effect is only a pass-through unless you've entered Freeze or PitchShift mode. Until then, the setSpeed() method (seemingly) does nothing. Did you activate one of these two modes?
 
The Granular effect is only a pass-through unless you've entered Freeze or PitchShift mode. Until then, the setSpeed() method (seemingly) does nothing. Did you activate one of these two modes?

Yes, I found that out by accident. Nevertheless thank you very much for your reply. I think I'll just write beginPitchShift(0) into my setup() or smth.

Another minor question that I have: The volume() method of the AudioControlSGTL5000 object can only be used a single time, right? At least I can't use it more often.
 
Another minor question that I have: The volume() method of the AudioControlSGTL5000 object can only be used a single time, right? At least I can't use it more often.

What gave you this impression that the volume can't be changed?

It can indeed be changed as often as you like, at least as fast as the relatively slow I2C protocol allows. If you look at the audio library tutorial, you'll see in part 1-5 (pages 6-7 in the PDF) there's an example where a knob is repeatedly read with analogRead() and the position used to adjust the volume.

If seeing (and hearing) is believing, you can see me & Alysia demo that volume changing code in the video. Skip forward to 6:14 in the video for just the volume change while the audio library is playing a WAV file from the SD card.

 
It suddenly worked when I booted it up this morning. ¯\_(ツ)_/¯

Also I'm having issues with my breadboard (MB-102). I can't use a lot of pins because they just won't connect to it properly. Any tips?
 
@PaulStoffregen

Thank you for your reply. I got that impression because it simply didn't work at the time I was writing my reply. Even the Serial Monitor showed the right value, which I passed to the volume() method.
On the other hand, I've already used the volume() method multiple times in a sketch successfully, so I guess my response was premature.
Having said this, I would take it with a grain of salt, since I'm still a beginner and probably not aware of a few things.
 
Ok (almost) everything works now. I resolved the "issue" with the breadboard.
The only problem that is left is the samples glitching out whenever they overlap each other. I assume that is because of the Audioprocessor handling multiple granular effects at the same time?
 
Last edited:
Status
Not open for further replies.
Back
Top