"Momentum" function

Hi all,

I am building an arduino synthesizer and am struggling with a function that I want to include.

I am trying to emulate the concept of bellows in the instrument. For example on a pump organ, you pump a pedal which fills a bag of air and then slowly releases it through the reeds, quickly at first and then more slowly as the air runs out. This makes the volume instrument pulse with the pumping of the footpedal.

I have an expression pedal (just a potentiometer in a pedal) hooked up to boost the volume of the synth, but I am struggling with the "momentum" function i.e. something that would emulate a bag filling with air and slowly releasing.

Does anyone have any ideas for a software function for this idea?

Would hooking up the right capacitor from ground to the wiper of the pot be a viable alternative? I'd rather have a software solution, since I could change the parameters without soldering, but I could go for a capacitor if it worked well.

Thanks for any advice!

running average of the inputted pulses. Basically a data pipe that you stuff in data and ti returns the avarage of N data-points.

-jim lee

Actually… I have one.

Your .h file…

#ifndef runningAvg_h
#define runningAvg_h

class runningAvg {
  
  public:
    runningAvg(int inNumData);
    ~runningAvg(void);
    
    float addData(float inData);
    float getAve(void);
    
  protected:
    int    maxData;     // Total amount allowed.
    int    numValues;   // The amount we have.
    int    index;       // Write the next value, here.
    float  *theValues;  // The array of values.
    float  result;      // Just in case they ask, we'll keep a copy.
};

#endif

And your .cpp file

#include "runningAvg.h"
#include <stdlib.h>

runningAvg::runningAvg(int inNumData) {
  
  theValues = (float*) malloc(sizeof(float)*inNumData);
  maxData = inNumData;
  numValues = 0;
  index = 0;
  result = 0;
}

 
runningAvg::~runningAvg(void) {
  
  free(theValues);
  theValues = NULL;
}



float runningAvg::addData(float inData) {
  
  float sum;
  
  if (numValues<maxData) {          // Early stages while filling.
    theValues[index++] = inData;    // Never been full so index must be ok.
    numValues++;
  } else {
    if (index==maxData) {           // Meaning its pointing past the array.
      index = 0;                    // Cycle around.
    }
    theValues[index++] = inData;    // And stuff the value in.
  }
  sum = 0;
  for (int i=0;i<numValues;i++) {   // We loop up to numValues but not including numValues.
    sum = sum + theValues[i];
  }
  result = sum/numValues;
  return result;
}

float runningAvg::getAve(void) { return result; }

So you can try it.

create the object with the number of data-points you’d like to average over, then start feeding in the data. Each fed in data point returns the current average.

Hope this helps.

-jim lee

Its a bit like a space ship on a very old 8 bit game. You can go forward by just +1, faster +2...
But you want a bit of dynamics, so instead of adding a number you add a changing variable.
That value has damping, an autonomic decree in value, the rate of the decrease being the damping value.
You have a maximum increase value but you start from zero and a time variant value increments this increase value at a determined rate. i.e the longer you hold the go button the fast the ship gets until it reaches maximum speed.
When you release the go button, the ship takes time to come to a halt as the increase value decreases in value by a determined rate.

OP: I imagine that effect (like the RC circuit you mentioned) could be modeled by a decaying exponential. But, you’d want to find a way to approximate that using integer math.

jimLee: Thank you for the advice! I think that's will help.

I think the possible complication is that that filling up the "bag" is quicker than releasing the air. To fill up the bag, there is just a quick push of the pedal, where as the air will release over a much longer period of time. With the datapoint function I think it would be a symmetrical of time required to fill the bag as to release the air.

I'll have to think about it more when I get some time tonight. I'm having a hard time conceptualizing it currently. Maybe I am overthinking it.

EVP: Exactly! The physical analogues are endless too: a claywheel with a pedal, a flywheel from a car, a capacitor in a circuit...

How about a rectifier?

Charges up instantly when the input is higher than the output, but when the input is lower the output decays over a much longer period of time.

I could easily lash up a discrete filter to mimic that.

matthewoconnell:
I am trying to emulate the concept of bellows in the instrument. For example on a pump organ, you pump a pedal which fills a bag of air and then slowly releases it through the reeds, quickly at first and then more slowly as the air runs out. This makes the volume instrument pulse with the pumping of the footpedal.

You could model this as a simple dynamical system.

Say for example we denote “x” as the input (rate of pumping) and “P” as the bag pressure. Then we could model the rate of change of P as increasing in proportion to “x”, but also decreasing in proportion to “P” (assuming the rate at which air escapes is proportional to the pressure). This gives an equation like:

Change_in_P = ax - bP, where “a” and “b” are some constants.

So the final difference equation is just P(k+1) = P(k) + Change_in_P(k). Where P(k) denotes the pressure at some sample point, and P(k+1) the value at the next.

The overall equation is then:

P(k+1) = (1-b)P(k) + ax(k).

That equation btw is just the common exponential low pass filter. The parameter “b” controls the rate of decay and the parameter “a” controls the gain. The rate at which this system decays is (1-b)**k (where ** denotes exponentiation). So parameter “b” should be between 0 and 1, with values larger values giving faster decay, and values near zero giving slower decay.

Normally as a filter we make a=b so that it’s unity gain, but in your case you could adjust “a” depending on the relative values of “x” and “P” that you want. I’m not even sure what your input is, it could be x=1 when pumping and x=0 when not pumping for example. But there are other alternatives there.

BTW I forgot to add. The dynamic behavior of any type of filter will depend upon the rate at which you sample (the number of times per second it gets updated in the main loop). In the above example the actual (real time) decay is (1-b)**(t/T) , where T is the sample period.

So for consistent results you really should execute any filter code at a fixed rate rather than just "free running" (as often as the main loop gets repeated). The easy way to do this is to just leave a section of your main loop that only executes on a given number of milliseconds.

For example

unsigned lastMillis, currentMillis;

void setup {
  lastMillis = millis();
  ...
}
void loop {
  ...   // Do whatever code you want running all the time here
  currentMillis = millis();
  if (currentMillis - lastMillis >= 20) {
    ... // Do code you want run every 20 ms here
    lastMillis = currentMillis;
  }
}

Would you like to just do a curve fit over time on a set curve? Basically sketch the shape you want, plot some (psi,time) points then map p=f(t)? loop at the end of every pump cycle.

-jim lee

stuart0:
For example

void loop {

...  // Do whatever code you want running all the time here
  currentMillis = millis();
  if (currentMillis - lastMillis >= 20) {
    ... // Do code you want run every 20 ms here
    lastMillis = currentMillis;
  }
}

const int intervalLengthFilter = 20;

void loop {
  ...   // Do whatever code you want running all the time here
  currentMillis = millis();
  if (currentMillis - beginningFilterInterval >= lengthIntervalFilter) {
    beginningFilterInterval += intervalLengthFilter;
    ... // Do code you want run every 20 ms here
  }
}

3 changes:

  • Interval advancement is moved to just after the interval check in the if block. I like to keep related bits of code as close to each other as physically possible.
  • Better names for the timing variables. I hate stupidly generic names like lastMillis or prevButtonState.
  • Instead of overwriting the old time with the new, advance the beginning of the interval by the interval length. millis() doesn't always increment by 1, and this method better averages out timing errors that could be introduced by that artifact.

Thanks, all!

This equation worked quite nicely for me.

P(k+1) = (1-b)P(k) + ax(k).

For anyone interested, here’s the final code I ended up with.

const int lengthIntervalFilter = 300;
unsigned beginningFilterInterval, currentMillis;
float currentBellowsPressure = 0;
float lastBellowsPressure = 0;
const float BELLOWS_RELEASE_CONSTANT = .5;
const float BELLOWS_FILL_CONSTANT = .6;

void loop {
  currentMillis = millis();
  if (currentMillis - beginningFilterInterval >= lengthIntervalFilter) {
    beginningFilterInterval += lengthIntervalFilter;
    float footpedal = (float)analogRead(A20);
    currentBellowsPressure = (1 - BELLOWS_RELEASE_CONSTANT) * lastBellowsPressure +     BELLOWS_FILL_CONSTANT * footpedal;
    if (currentBellowsPressure > 1024) {
      currentBellowsPressure = 1024.00;
    }

    Serial.println( currentBellowsPressure );
    Serial.println( footpedal );
    lastBellowsPressure = currentBellowsPressure;
  }

}

The footpedal is just a pot hooked up to an analog pin.

I put a max pressure on the bellows and am still messing with the FILL / RELEASE constants.

Thank you so much for all of the advice and brainstorming.

PS:

While this is working well for my purposes, it's still not exactly mimicking bellows.

ie:
In a real pump organ, the action of pumping the bellows fills the bag with air.
In this system, pushing the pedal down (pot wiper connected to 3.3V) is what fills the bag.

It's similar enough for the time being, but I guess an actual "analog"* would actually detect the pumping action.

If anyone has any ideas, feel free to chime in.

  • not analog in the normal electronics sense, but a digital analog to the physical system.

Thanks again.

Actually, I believe its vacuum.

-jim lee

matthewoconnell:
PS:

While this is working well for my purposes, it’s still not exactly mimicking bellows.

ie:
In a real pump organ, the action of pumping the bellows fills the bag with air.
In this system, pushing the pedal down (pot wiper connected to 3.3V) is what fills the bag.

I think the problem is that in the current setup the position of the pot is mimicking the flow of air, whereas it probably should be the velocity (rate of change of the pot) that is the real analog for air flow.

So we need to use the difference in input pot position “x” rather than just its present value. One problem with that simple approach however, is that the change will be both positive and negative over a cycle, and you’ll get no net air flow. So you’ll need to either use the absolute value of the change (which actually models a double acting cylinder) or use just positive changes and ignore negative ones (which is a normal single acting pump). I’ll assume the latter in the code below.

Now our change in pressure is of the form

if (x>lastx) Change_in_P = -bP + a(x - last_x); else Change_in_P= -b*P;

This leads to the new equation:

if (x>xlast) P = (1-b)*P + a*(x - lastx);
else P = (1-b)*P;

You’ll probably have to re-tune the constant “a” for the new model.

I modified your code with the same idea as stewart’s.

const int lengthIntervalFilter = 300;
unsigned beginningFilterInterval, currentMillis;
float currentBellowsPressure = 0;
float lastBellowsPressure = 0;
const float BELLOWS_RELEASE_CONSTANT = .5;
const float BELLOWS_FILL_CONSTANT = .6;
int16_t previous_footpedal;

void setup()
{
	previous_footpedal = analogRead(A20);
}

void loop {
  currentMillis = millis();
  if (currentMillis - beginningFilterInterval >= lengthIntervalFilter) {
    beginningFilterInterval += lengthIntervalFilter;
    int16_t current_footpedal = (float)analogRead(A20);

	// might need to change the polarity of the following difference.
	// Does the analogRead() value go up or down when you press it? This code assumed it increases.
    int16_t foot_pedal_difference = current_footpedal - previous_footpedal; // detect the change in the footpedal level;
    if( foot_pedal_difference<0 )
        foot_pedal_difference = 0; // half-wave rectify to simulate one-way air valves.
	previous_footpedal = current_footpedal;

    currentBellowsPressure = (1 - BELLOWS_RELEASE_CONSTANT) * lastBellowsPressure +     BELLOWS_FILL_CONSTANT * (float)foot_pedal_difference;
    if (currentBellowsPressure > 1024) {
      currentBellowsPressure = 1024.00;
    }

    Serial.println( currentBellowsPressure );
    Serial.println( footpedal );
    lastBellowsPressure = currentBellowsPressure;
  }

}

It’s not really necessary to detect the velocity of the pot’s changes (that would imply some sort of numerical differentiation, which is more complicated), just the changes in the value itself (which only requires subtraction). From there, the change value is half-wave rectified to get rid of the negative value. This implies a single-action pump like stuart mentioned, when only one direction fills the bellows. Then that value is put into the bellows pressure calculation.

I have not tested this code.