Applying Exp and Log curves to linear sensor data (Midi Controller)

Status
Not open for further replies.

bjosephs

Active member
Hi All,



I'm looking for some very general guidance on how to convert lineal sensor measurements (10-bit) into curves of varying intensity. I have a set of force sensing resistors that I'm making a MIDI instrument from and I want to try some different ways of tuning their sensitivity. I've done some simple stuff like raising the lowest available value and lowering the max value needed to clip at the highest midi note velocity but it feels like I need something more precise. This link shows how a commercial product applies logarithmic and exponential curves to the data to change the sensitivity:

https://www.noterepeat.com/products/akai-professional/mpc2500?id=146

I looked through my notes and found a formula that I'd use for some digital potentiometer stuff but it was just for calculating value in a spreadsheet and would use floating point math in it's current state. It takes percentages from 0 to 1.0 and converts them to a value that fits a curve of some intensity. I can't find the original reference but either the .0125 or 81 could be changed to adjust the curve intensity.Of course this only achieves a log curve (lower sensitivity) not a Exp (for higher sensitivity).

=0.0125 *(81^linearpercentage) - 0.0125

There's also the possibility of "pseudo-log" (borrowing a term from the analog world) where the curve is simplified to a couple of straight lines that approximate said curve. This is what log potentiometers do to ease manufacturing. Like this:

iu.jpeg

I'm reading 16 analog values, conditioning them, and returning for usbMIDI to send them out when back in the loop. This is done in an ISR so I want to keep the math tight. Some details:

  • The data is 10 bit as mentioned above
  • The range is narrower than 0-1023 because I set a threshold at the bottom for noise and a pull down resistor prevents getting fully to 1023
  • The final value needs to be from 0-127 for the midi standard
  • I'd like to acheive close to the full 127 values of precision. I think the MPC product I linked to uses 10 values which is fine for a simple drum machine but I'm using real time pressure manipulation for additional expressive controls

So I'm just looking for some general brainstorm ideas on how more experienced people might do something like this.


Brian
 
I'm reading 16 analog values, conditioning them, and returning for usbMIDI to send them out when back in the loop. This is done in an ISR so I want to keep the math tight.

Given the data is only 10 bit you might pre-compute a lookup table so the ISR is very fast, should that be needed.
 
this link is about volume control but some approximations that might be of use to you.
cheers, Paul

Thanks. I ended up playing the function this author described for a bit while learning to use log and Exp type functions. There were pros and cons. In the end, they gave a lot of precision but I wasn't able to figure out to to scale my input data off of the curves. For example getting a log2(x) value off of a percentage of 10 bit data yielded a range for -6.xxxxx to 0.; a different base gave a shallower or steeper curve (great) but a very different range to try and scale off of. Got troublesome so I focused on "pseudo" curve with two lines.
 
Given the data is only 10 bit you might pre-compute a lookup table so the ISR is very fast, should that be needed.

At 10 bits this would be a table with 1024 corresponding unsigned int values? I guess since I only need 7 bits of precision for midi that I could also use a table of 128 byte values.
 
It took longer than it probably should have but after some refreshing on y=mx+b I have a function that does what I wanted. I'm still evaluating if it gives me the results I need at the instrument level. The code below generates 0-1023 of input data and runs each point through the function. The user gives a gain and transition point (below which the gain is applied) and then corrects the slope above the transition point so the output intersects 1023. The gain and transition are sufficiently arbitrary for me for now, you can also put gains below 1 to invert the curve. Feeling pretty good right now!


HTML:
/* bjosephs 2/6/2022
 * condition_data function applies a pseudo curve to input data
 * User inputs a tartget gain (howMuch) and location (where) to apply gain below  
 * and the funciton calculates a vertex and slope to ensure the output tops out 1023
 */
 
float result;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  for (unsigned int i = 0; i < 1024; i++)
  {
    result = condition_data(i, 1023, 200, .6);
    Serial.print(i);
    Serial.print("\t");
    Serial.print(result);
    Serial.println();
  }
}

float condition_data(unsigned int input,  unsigned int clip, unsigned int where, float howMuch)
{
  static float val;
  static double slope; //slope for upper data
  static float b;

  slope = (1023 - (where * howMuch)) / ( 1023 - (where)); //rise over run for new slope
  b = (where * howMuch) - (slope * where);

  if (input < where)
  {
    val = input * howMuch;
  }
  else
  {
    val = (input * slope) + b;
  }

  if (val > clip)
  {
    val = clip;
  }
  return  val ;
}


void loop() {
  // put your main code here, to run repeatedly:
}
 
Last edited:
I realized that by calculating the slope but using (1023,1023) was creating a problem for myself because my sensor never quite gets the full ADC range. I masked this by bringing headroom down to cut off the top of the second slope at some lower value, then scale the output to 127 based on division or the Arduino map function. In this new example I calculate the "run" in rise over run based on the headroom but the rise is still based on 1023. The result is that the second slope aspires to reach 1023 at a lower value, will overshoot, and will need to be capped a bit further down. What is nice about that is the output now clips at the highest useful value and can now be scaled from 10 bits to 7 with a simple bit shift, rather than another floating point division that has to be based on the arbitrary headroom value.


HTML:
/* bjosephs 2/6/2022
   condition_data function applies a pseudo curve to input data
   User inputs a tartget gain (_gain) and location (_transition) to apply gain below
   and the funciton calculates a vertex and slope to ensure the output tops out 1023
*/

float result;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);

  for (unsigned int i = 0; i < 1024; i++)
  {
    result = condition_data(i, 700, 150, 4.0);
    Serial.print(i);
    Serial.print("\t");
    Serial.print(result);
    Serial.println();
  }
}

float condition_data(unsigned int input,  unsigned int _headroom, unsigned int _transition, float _gain)
{
  static float val;
  static double slope; //slope for upper data
  static float b;

  slope = (1023 - (_transition * _gain)) / ( _headroom - (_transition)); //rise over run for new slope
  b = (_transition * _gain) - (slope * _transition);

  if (input < _transition)
  {
    val = input * _gain;
  }
  else
  {
    val = (input * slope) + b;
  }

  if (val > 1023)
  {
    val = 1023;
  }

  val = (unsigned int) val >> 3;

  return  val ;
}


void loop() {
  // put your main code here, to run repeatedly:
}

Screen Shot 2022-02-09 at 7.05.17 AM.png
 
Status
Not open for further replies.
Back
Top