Go Down

Topic: "Momentum" function (Read 1 time) previous topic - next topic

matthewoconnell

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!

jimLee

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
PNW Ardiuno & Maker club
1012 9Th Street, Anacortes, WA 98221 (Around the back of building)

jimLee

#2
Feb 07, 2017, 09:07 pm Last Edit: Feb 07, 2017, 09:09 pm by jimLee
Actually.. I have one.

Your .h file..

Code: [Select]

#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

Code: [Select]
#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
PNW Ardiuno & Maker club
1012 9Th Street, Anacortes, WA 98221 (Around the back of building)

EVP

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.

gfvalvo

#4
Feb 07, 2017, 09:48 pm Last Edit: Feb 07, 2017, 09:53 pm by gfvalvo
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.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

matthewoconnell

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


Jiggy-Ninja

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.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

stuart0

#7
Feb 08, 2017, 05:32 am Last Edit: Feb 08, 2017, 07:36 am by stuart0
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 = a*x - b*P, 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) + a*x(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.

stuart0

#8
Feb 08, 2017, 05:46 am Last Edit: Feb 08, 2017, 05:54 am by stuart0
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
Code: [Select]

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;
  }
}

jimLee

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
PNW Ardiuno & Maker club
1012 9Th Street, Anacortes, WA 98221 (Around the back of building)

Jiggy-Ninja

For example
Code: [Select]

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;
  }
}


Code: [Select]

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.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

matthewoconnell

Thanks, all!


This equation worked quite nicely for me.

P(k+1) = (1-b)*P(k) + a*x(k).

matthewoconnell

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


Code: [Select]

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.

matthewoconnell

#13
Feb 12, 2017, 09:34 pm Last Edit: Feb 12, 2017, 09:39 pm by 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.

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.



jimLee

Actually, I believe its vacuum.

-jim lee
PNW Ardiuno & Maker club
1012 9Th Street, Anacortes, WA 98221 (Around the back of building)

Go Up