Trigger adEnv on value change

Hi

I am working with a sensor that uses body resistance and I would like to make it trigger the envelope when the value of the sensor changes by a threshold value.

The goal of this is that I want the sound to happen when the sensor is being touched but not otherwise and this is the only way I can think of to do this. Here is the code that I am using to measure if there is a difference. (I will paste the full code below)

 currentValue = analogRead(analogPin);
    if (abs(currentValue - previousValue) >= hysteresis)
    {
      adenv.Trigger();
      //update the previous value
      previousValue = currentValue;
      
    }

Should I put this in the audio callback or in the main loop? The sound is very noisy when I put it in the audio callback and I am wondering if this is because measuring an analogue input inside the audio callback is not the way to do it. The sound is clear when I put it in the main loop. In both cases however it works for a bit and responds only when it is being touched but after a while it just stays on even when it is not touched.

Here is my full code, it’s a bit messy. Would anyone have ideas as to why it might be staying on and a better way I could achieve my goal of the sound only occurring when the sensor is touched.

// Title: adenv
// Description: Triangle wave controlled by AD envelope
// changed to work with body resistance instead of metro tick
// Hardware: Daisy Seed
// Author: Stephen Hensley

#include "DaisyDuino.h"

size_t num_channels;

DaisyHardware hw;

static Metro tick;
static AdEnv adenv;
static Oscillator osc;

// setting up tentacle
float pitchknob;

const byte analogPin   = A1; // tentacle in

//change this value to what's needed
const byte hysteresis  = 60;

float currentValue;
float previousValue;

void MyCallback(float **in, float **out, size_t size) {
  for (size_t i = 0; i < size; i++) {
    // if (tick.Process()) {
    //   adenv.Trigger();
    // }
    

    float env_out = adenv.Process();
    float sig = osc.Process();
    sig *= env_out;

    for (size_t chn = 0; chn < num_channels; chn++) {
      out[chn][i] = sig;
    }
  }
}

void setup() {
  float sample_rate;
  // Initialize for Daisy pod at 48kHz
  hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
  num_channels = hw.num_channels;
  sample_rate = DAISY.get_samplerate();

  adenv.Init(sample_rate);
  tick.Init(1, sample_rate);
  osc.Init(sample_rate);

  // Set oscillator parameters
  osc.SetFreq(440);
  osc.SetAmp(0.5);
  osc.SetWaveform(osc.WAVE_TRI);

  // Set envelope parameters
  adenv.SetTime(ADENV_SEG_ATTACK, 0.05);
  adenv.SetTime(ADENV_SEG_DECAY, 0.15);
  adenv.SetMin(0.0);
  adenv.SetMax(0.25);
  adenv.SetCurve(0); // linear

  DAISY.begin(MyCallback);
  //Serial.begin(9600);
}

void loop() {
  currentValue = analogRead(analogPin);
    if (abs(currentValue - previousValue) >= hysteresis)
    {
      adenv.Trigger();
      //update the previous value
      previousValue = currentValue;
      
    }
  //Serial.println(currentValue);
  
}

Hey Tara! Hope you have been well :slight_smile:

You can have analogRead() in MyCallback() (not inside the for-loop though) and it’s encouraged.
But if it sounds fine when you have those lines in the void loop(), it may end up being fine.
What did your code look like when you had those lines in MyCallback()?

I’m guessing the previousValue variable may not be updating correctly. Could you print it out and see how it changes/updates overtime?

Hi Takumi

Thanks for your reply

I will check what the previousValue variable is doing.

Can you explain why an analogRead is better in the audio callback?

The way I had my code when it was in there was the same. I had the if statement controlling the trigger of the envelope. Similar to the metro that you can see commented out.

// Title: adenv
// Description: Triangle wave controlled by AD envelope
// changed to work with body resistance instead of metro tick
// Hardware: Daisy Seed
// Author: Stephen Hensley

#include "DaisyDuino.h"

size_t num_channels;

DaisyHardware hw;

static Metro tick;
static AdEnv adenv;
static Oscillator osc;

// setting up tentacle
float pitchknob;

const byte analogPin   = A1; // tentacle in

//change this value to what's needed
const byte hysteresis  = 60;

float currentValue;
float previousValue;

void MyCallback(float **in, float **out, size_t size) {
  for (size_t i = 0; i < size; i++) {
    // if (tick.Process()) {
    //   adenv.Trigger();
    // }
      currentValue = analogRead(analogPin);
    if (abs(currentValue - previousValue) >= hysteresis)
    {
      adenv.Trigger();
      //update the previous value
      previousValue = currentValue;
      
    }

    float env_out = adenv.Process();
    float sig = osc.Process();
    sig *= env_out;

    for (size_t chn = 0; chn < num_channels; chn++) {
      out[chn][i] = sig;
    }
  }
}

void setup() {
  float sample_rate;
  // Initialize for Daisy pod at 48kHz
  hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
  num_channels = hw.num_channels;
  sample_rate = DAISY.get_samplerate();

  adenv.Init(sample_rate);
  tick.Init(1, sample_rate);
  osc.Init(sample_rate);

  // Set oscillator parameters
  osc.SetFreq(440);
  osc.SetAmp(0.5);
  osc.SetWaveform(osc.WAVE_TRI);

  // Set envelope parameters
  adenv.SetTime(ADENV_SEG_ATTACK, 0.05);
  adenv.SetTime(ADENV_SEG_DECAY, 0.15);
  adenv.SetMin(0.0);
  adenv.SetMax(0.25);
  adenv.SetCurve(0); // linear

  DAISY.begin(MyCallback);
  //Serial.begin(9600);
}

void loop() {

  //Serial.println(currentValue);
  
}

I’ll do my best to explain!
MyCallback() is an audio callback where things are happening at audio rate which is faster than the control rate in loop().
If you, for example, read potentiometer value at a control rate as you twist it, you may get a shape that’s more like a stair-step rather than a smooth curve. Especially when mapping certain sensor to an amplitude at that rate, you’ll hear the “zipper” noise.

That being said, it may not matter too much for your code.
I am curious how it’ll sound if you put those lines of code with adenv.Trigger(); outside of the for-loop while keeping it inside of the MyCallback.

Another approach that you could take for your project is using the fonepole() function. This function will smooth out your sensor value.
For example, you can have something like fonepole(currentValue, currentValueTarget, .001); after reading the sensor value and then map currentValueTarget to the amplitude of your sound (of course, you may need to scale to a range between 0.0 and 1.0). When you touch the sensor and the value changes, it’ll be smoothed out so you shouldn’t hear any clicking. This worked nicely when I mapped a button press value to an amplitude in a direct way. Without smoothing, the value jumped from 0.0 to 1.0 too fast so there was a clicking noise.