Noob Question on map function

jp2345

Member
Hi,

I am working on a breath controller for my VST's inside my DAW. I am using a Freescale MPX5010GP (following on from this projecthttps://hackaday.io/project/12315/files) into a Teensy 3.1. I have successfully got the pressure sensor to trigger CC1 (Modulation) on my VST's but I am looking to improve the responsiveness of the sensor so it's not so hard to blow.

I have used the serial data to capture the lowest value of 46 and the highest value of 866. How can I use the map function to change the responsiveness?

Here is the code I am using


Code:
/* USB MIDI Breath Controller Sending Midi to CC1 - Modulation

   You must select MIDI from the "Tools > USB Type" menu
   http://www.pjrc.com/teensy/td_midi.html

   This example code is in the public domain.
*/

#include <Bounce.h>

// the MIDI channel number to send messages
const int channel = 1;

// the MIDI continuous controller for each analog input

const int controllerA1 = 1; // 1 = Modulation Wheel


void setup() {
}

// store previously sent values, to detect changes

int previousA1 = -1;



elapsedMillis msec = 0;

void loop() {
  // only check the analog inputs 50 times per second,
  // to prevent a flood of MIDI messages
  if (msec >= 20) {
    msec = 0;
    
    int n1 = analogRead(A1) / 8;
    /* Map an analog value to 8 bits (0 to 255) */
  
   
    // only transmit MIDI messages if analog input changed
   
    if (n1 != previousA1) {
      usbMIDI.sendControlChange(controllerA1, n1, channel);
      previousA1 = n1;
    
    }
  }

  // MIDI Controllers should discard incoming MIDI messages.
  // http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}
 
Couple of tips:-
Code:
void loop() {
  // only check the analog inputs 50 times per second,
  // to prevent a flood of MIDI messages
  if (msec >= 20) {
    msec = 0;
    
    int n1 = analogRead(A1);

    /* removing  / 8; because this divides the read value by 8 so resolution is lost
    before map() gets to see it */
    
    /* Map an analog value to 7 bits (0 to 127) as the Min / Max values for Midi CC messages 
       is typically 0 - 127 so map() neeeds to look like:- */
    
    map(n1, 46, 866, 0, 127); //Should map the full input range to CC# range
   
    // only transmit MIDI messages if analog input changed
   
    if (n1 != previousA1) {
      usbMIDI.sendControlChange(controllerA1, n1, channel);
      previousA1 = n1;
    
    }
  }
  // MIDI Controllers should discard incoming MIDI messages.
  // [url]http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash[/url]
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}

Hope this helps.
 
Couple of tips:-
...
Hope this helps.

Should be: n1 = map(n1, 46, 866, 0, 127); //Should map the full input range to CC# range

Code:
void loop() {
  // only check the analog inputs 50 times per second,
  // to prevent a flood of MIDI messages
  if (msec >= 20) {
    msec = 0;
    
    int n1 = analogRead(A1);

    /* removing  / 8; because this divides the read value by 8 so resolution is lost
    before map() gets to see it */
    
    /* Map an analog value to 7 bits (0 to 127) as the Min / Max values for Midi CC messages 
       is typically 0 - 127 so map() neeeds to look like:- */
    
    [B]n1 = map(n1, 46, 866, 0, 127); //Should map the full input range to CC# range[/B]
   
    // only transmit MIDI messages if analog input changed
   
    if (n1 != previousA1) {
      usbMIDI.sendControlChange(controllerA1, n1, channel);
      previousA1 = n1;
    
    }
  }
  // MIDI Controllers should discard incoming MIDI messages.
  // [url]http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash[/url]
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}
 
This is the description: reference.arduino.cc/reference/en/language/functions/math/map/

Putting the observed low=46 and high=866 as the right parameters then supplying the desired range for the other two numbers the returned value will fall proportionately between the 'desired' based on the percentage between the observed value.

See Improved map() on this page: pjrc.com/teensyduino-1-54-released/ for details on PJRC's improvements that may help/apply.

I think that worked. Does this look correct?

Code:
/* USB MIDI Breath Controller Sending Midi to CC1 - Modulation, CC2 Vibrato

   You must select MIDI from the "Tools > USB Type" menu
   http://www.pjrc.com/teensy/td_midi.html

   This example code is in the public domain.
*/

#include <Bounce.h>

// the MIDI channel number to send messages
const int channel = 1;

// the MIDI continuous controller for each analog input

const int controllerA1 = 1; // 1 = Modulation Wheel


void setup() {
}

// store previously sent values, to detect changes

int previousA1 = -1;



elapsedMillis msec = 0;

void loop() {
  // only check the analog inputs 50 times per second,
  // to prevent a flood of MIDI messages
  if (msec >= 20) {
    msec = 0;
    
    int n1 = analogRead(A1) / 8;
    Serial.println(map(n1, 46, 886, 1, 127));
  
   
    // only transmit MIDI messages if analog input changed
   
    if (n1 != previousA1) {
      usbMIDI.sendControlChange(controllerA1, n1, channel);
      previousA1 = n1;
    
    }
  }

  // MIDI Controllers should discard incoming MIDI messages.
  // http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}
 
Make some sounds with it so your ears give feedback and play with a Midi monitor like MidiOx or whatever suits your setup.
 
I think that worked. Does this look correct?
...



See post #4 - the Map value is being printed but not used. after printing put a line for: n1 = map(n1, 46, 866, 0, 127); //Should map the full input range to CC# range
- or try as edited below ...

Code:
/* USB MIDI Breath Controller Sending Midi to CC1 - Modulation, CC2 Vibrato

   You must select MIDI from the "Tools > USB Type" menu
   http://www.pjrc.com/teensy/td_midi.html

   This example code is in the public domain.
*/

#include <Bounce.h>

// the MIDI channel number to send messages
const int channel = 1;

// the MIDI continuous controller for each analog input

const int controllerA1 = 1; // 1 = Modulation Wheel


void setup() {
}

// store previously sent values, to detect changes

int previousA1 = -1;



elapsedMillis msec = 0;

void loop() {
  // only check the analog inputs 50 times per second,
  // to prevent a flood of MIDI messages
  if (msec >= 20) {
    msec = 0;
    
    int n1 = analogRead(A1) / 8;
[B] n1 = map(n1, 46, 866, 0, 127); //Should map the full input range to CC# range
    Serial.println(n1);[/B]
  
   
    // only transmit MIDI messages if analog input changed
   
    if (n1 != previousA1) {
      usbMIDI.sendControlChange(controllerA1, n1, channel);
      previousA1 = n1;
    
    }
  }

  // MIDI Controllers should discard incoming MIDI messages.
  // http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}
 
Thanks Again. The only problem I see now is that even with the remapping I am getting a reading of 10 on a midi monitor. With this in mind i changed the range to min 10 and max 866 but still the only way to get a zero reading is to suck on the pressure monitor Screen Shot 2023-04-20 at 7.57.13 AM.png
 
Seems the low value may need to be conditioned.
If 46 is indeed the low value expected to be the ZERO point, and negative/suck pressure is ever expected/accepted try:

Code:
    int n1 = analogRead(A1) / 8;
[B] if (n1<46) n1=46;[/B]
 n1 = map(n1, 46, 866, 0, 127); //Should map the full input range to CC# range
    Serial.println(n1);
 
Thank you for all your assistance. It still wasn't working but then I played around with the numbers until I got it to work as I wanted. Here is what I change the condition to in the end:
Code:
 int n1 = analogRead(A1) / 8;
    if (n1<10) n1=1;
    Serial.println(map(n1, 46, 886, 1, 127));
 
Great it got to working! Not shown that the return value of map() is assigned to n1=map().

Without assigning that value n1 won't be changed for use after that and only the original value of n1 will be used.
 
I'm still trying to make the best controller I can and came across this code from https://www.hackster.io/costis/arduino-breath-controller-for-cheap-usb-midi-fe3a8f
But it was written for a different Arduino board. How do I translate this for Teensy?
Code:
/*
  Breath Controller
*/

//Libraries used - install them from Tools->Manage Libraries
#include <Oversampling.h>
#include <USB-MIDI.h>

//Debug mode (uncomment to enable)
//#define DEBUG 1

//Creation of the USB MIDI interface
USBMIDI_CREATE_DEFAULT_INSTANCE();

//Oversampling init
Oversampling adc(10, 13, 6);

// ***************** User Setup ***************** 
// Values ending in 1 correspond to blowing while those ending in 2 to drawing in air
// Pin setup
const int sensorPin1 = A0;    // select the Arduino input pin for the Sensor/Op Amp output

// Range Calibration. Adjust this manually so that you can reach maximum but not too easily.
int sensorRange1 = 800;
int sensorRange2 = 800;

// Output controller number. Select from below table
// 0-127: regular control change messages
// 128: monophonic aftertouch
// 129: Pitch Bend Up 
// 130: Pitch Bend Down 
int controllerNumber1 = 2;  // Controller sent when blowing
int controllerNumber2 = 2;  // Controller sent when drawing in air


// Output controller channels
int controllerChannel1 = 1;
int controllerChannel2 = 1;

// Safety thresholds for lowest and highest values to avoid fluctuations when at rest or max. 
// If multiple messages are sent when at rest increase the lowThreshold. 
// If multiple messages are sent when at max increase the highThreshold. 
const int lowThreshold1 = 5;
const int lowThreshold2 = 5;

const int highThreshold1 = 0;
const int highThreshold2 = 0;

// Curve definition. Tables can have any legth equal or larger than 2. Values can be 0-127 . Tables should have the same number of elements and "in" tables should be in ascending order.
// Conversions are done at a readings level so loss of definition is minimized.
int in1[]   = {0, 127};
int out1[]  = {0, 127};

int in2[]   = {0, 127};
int out2[]  = {0, 127};

// Example curves (modify sensor number accordingly)

//Soft
//int in1[]   = {0, 6,24,78,127};
//int out1[]  = {0,32,64,96,127};

// Reduced range
//int in1[]   = {50, 100};
//int out1[]  = {50, 100};

// Refresh Cycle (milliseconds). Lower values mean more messages are sent during operation.
int refreshCycle = 0;

//  ***************** Implementation  ***************** 
// Do not modify from this point and onward if you do not intent to alter the operation of the sensor.

// Internal Value of Sensors
int sensorValue1 = 0;
int sensorValue2 = 0;

// Minimum sensor values 
int sensorMin1;
int sensorMin2;

// Output controller values
int controllerValue1 = 0;
int controllerValue2 = 0;

// Previous cycle values used to avoid repetition of identical messages
int previousControllerValue1 = 0;
int previousControllerValue2 = 0;

// Range conversion variable init
int outputRange1;
int outputRange2;

int sensorLow1;
int sensorLow2;

int sensorHigh1;
int sensorHigh2;

void setup() {
  MIDI.begin(1);

#ifdef DEBUG
  Serial.begin (115200); //Only for debug mode
#endif

// Calibrate sensor's rest point by averaging 10 first values. Do not use the sensor while booting the device.
  sensorMin1 = adc.read(sensorPin1);
  sensorMin2 = 0;

// Determine output ranges for the controllers chosen
  outputRange1 = outputRange(controllerNumber1);
  outputRange2 = outputRange(controllerNumber2);
}

void loop() {
// read the value from the sensor:
  sensorValue1 = adc.read(sensorPin1); // Blowing air
  sensorValue2 = sensorMin1 - sensorValue1; // Drawing in air

// Store previous values
  previousControllerValue1 = controllerValue1;
  previousControllerValue2 = controllerValue2;
  
// Usable range limits for sensor up/down
  sensorLow1 = sensorMin1 + lowThreshold1;
  sensorLow2 = sensorMin2 + lowThreshold2;

  sensorHigh1 = sensorLow1 + sensorRange1 - highThreshold1;
  sensorHigh2 = min(sensorMin1,sensorRange2) - highThreshold2;

// Convert internal values to output range (0..127 for controllers/aftertouch 0..+/-8191 for Pitchbend Up/Down) using the curves defined in "in" and "out" tables.
  controllerValue1 = map(mapToCurve(constrain(sensorValue1,sensorLow1,sensorHigh1),sensorLow1,sensorHigh1,in1,out1,sizeof(in1)/sizeof(int)),sensorLow1,sensorHigh1,0,outputRange1);
  controllerValue2 = map(mapToCurve(constrain(sensorValue2,sensorLow2,sensorHigh2),sensorLow2,sensorHigh2,in2,out2,sizeof(in2)/sizeof(int)),sensorLow2,sensorHigh2,0,outputRange2);

// Send MIDI messages  
  if (controllerValue1 != previousControllerValue1) sendSensorOutput(controllerNumber1, controllerValue1, controllerChannel1);
  if (controllerValue2 != previousControllerValue2) sendSensorOutput(controllerNumber2, controllerValue2, controllerChannel2);

// Debug

#ifdef DEBUG
// Sensor (input) values (uncomment for debug)
//  Serial.print (sensorValue1);
//  Serial.print (",");
//  Serial.print (sensorValue2);
//  Serial.print (",");
  
// Controller (output) values
  Serial.print (controllerValue1);
  Serial.print (",");
  Serial.println (controllerValue2);
#endif
  
// stop the program for for <refreshCycle> milliseconds:
  delay(refreshCycle);
}

// Function used to send MIDI messages according to controller number
void sendSensorOutput (int number, int value, int channel) {
  if (number < 128) MIDI.sendControlChange(number, value, channel);
  else if (number == 128) MIDI.sendAfterTouch(value, channel);
  else if (number == 129) MIDI.sendPitchBend(value, channel);
  else if (number == 130) MIDI.sendPitchBend(-value, channel);
}

// Function used to determine the range of a specific controller. This is due to the fact pitch bend has a larger range than regular controllers.
int outputRange (int number) {
  if (number > 128) return 8191;
  else return 127;
}

// Modified multiMap function used to create curves. Original by Rob Tillaart.
int mapToCurve(int val, int sensorLow, int sensorHigh, int* _in, int* _out, uint8_t size)
{
  // take care the value is within range
  // val = constrain(val, _in[0], _in[size-1]);
  if (val <= map(_in[0],0,127,sensorLow,sensorHigh)) return map(_out[0],0,127,sensorLow,sensorHigh);
  if (val >= map(_in[size-1],0,127,sensorLow,sensorHigh)) return map(_out[size-1],0,127,sensorLow,sensorHigh);

  // search right interval
  uint8_t pos = 1;  // _in[0] already tested
  while(val > map(_in[pos],0,127,sensorLow,sensorHigh)) pos++;

  // adjusting range from ..127 to sensor range
  int inPos = map(_in[pos],0,127,sensorLow,sensorHigh);
  int outPos = map(_out[pos],0,127,sensorLow,sensorHigh);
  int inPrv = map(_in[pos-1],0,127,sensorLow,sensorHigh);
  int outPrv = map(_out[pos-1],0,127,sensorLow,sensorHigh);

  
  // this will handle all exact "points" in the _in array
  if (val == inPos) return outPos;
  
  // interpolate in the right segment for the rest
  return ((long)val - (long)inPrv) * ((long)outPos - (long)outPrv) / ((long)inPos - (long)inPrv) + (long)outPrv;
}
 
Some small changes here, compiles for T3.6 may be a start.
Code:
/*
  Breath Controller
*/

//Libraries used - install them from Tools->Manage Libraries
#include <Oversampling.h>
//***********************************************************
//#include <USB-MIDI.h>
//Creation of the USB MIDI interface
//USBMIDI_CREATE_DEFAULT_INSTANCE();
//Both not required for Teensy usbMIDI

// Choose Tools> USB Type MIDI 
//***********************************************************

//Debug mode (uncomment to enable)
//#define DEBUG 1


//Oversampling init
Oversampling adc(10, 13, 6);

// ***************** User Setup ***************** 
// Values ending in 1 correspond to blowing while those ending in 2 to drawing in air
// Pin setup
const int sensorPin1 = A0;    // select the Arduino input pin for the Sensor/Op Amp output

// Range Calibration. Adjust this manually so that you can reach maximum but not too easily.
int sensorRange1 = 800;
int sensorRange2 = 800;

// Output controller number. Select from below table
// 0-127: regular control change messages
// 128: monophonic aftertouch
// 129: Pitch Bend Up 
// 130: Pitch Bend Down 
int controllerNumber1 = 2;  // Controller sent when blowing
int controllerNumber2 = 2;  // Controller sent when drawing in air


// Output controller channels
int controllerChannel1 = 1;
int controllerChannel2 = 1;

// Safety thresholds for lowest and highest values to avoid fluctuations when at rest or max. 
// If multiple messages are sent when at rest increase the lowThreshold. 
// If multiple messages are sent when at max increase the highThreshold. 
const int lowThreshold1 = 5;
const int lowThreshold2 = 5;

const int highThreshold1 = 0;
const int highThreshold2 = 0;

// Curve definition. Tables can have any legth equal or larger than 2. Values can be 0-127 . Tables should have the same number of elements and "in" tables should be in ascending order.
// Conversions are done at a readings level so loss of definition is minimized.
int in1[]   = {0, 127};
int out1[]  = {0, 127};

int in2[]   = {0, 127};
int out2[]  = {0, 127};

// Example curves (modify sensor number accordingly)

//Soft
//int in1[]   = {0, 6,24,78,127};
//int out1[]  = {0,32,64,96,127};

// Reduced range
//int in1[]   = {50, 100};
//int out1[]  = {50, 100};

// Refresh Cycle (milliseconds). Lower values mean more messages are sent during operation.
int refreshCycle = 0;

//  ***************** Implementation  ***************** 
// Do not modify from this point and onward if you do not intent to alter the operation of the sensor.

// Internal Value of Sensors
int sensorValue1 = 0;
int sensorValue2 = 0;

// Minimum sensor values 
int sensorMin1;
int sensorMin2;

// Output controller values
int controllerValue1 = 0;
int controllerValue2 = 0;

// Previous cycle values used to avoid repetition of identical messages
int previousControllerValue1 = 0;
int previousControllerValue2 = 0;

// Range conversion variable init
int outputRange1;
int outputRange2;

int sensorLow1;
int sensorLow2;

int sensorHigh1;
int sensorHigh2;

void setup() {
//***********************************************************
// MIDI.begin(1);
// Not required for Teensy usbMIDI
delay(1000); // to allow time for the Host PC to get ready to receive Midi messages
//***********************************************************

#ifdef DEBUG
  Serial.begin (115200); //Only for debug mode
#endif

// Calibrate sensor's rest point by averaging 10 first values. Do not use the sensor while booting the device.
  sensorMin1 = adc.read(sensorPin1);
  sensorMin2 = 0;

// Determine output ranges for the controllers chosen
  outputRange1 = outputRange(controllerNumber1);
  outputRange2 = outputRange(controllerNumber2);
}

void loop() {
//****************************************************************
 while (usbMIDI.read()) {
     // controllers must call .read() to keep the queue clear even if they are not responding to MIDI
  }

//****************************************************************

// read the value from the sensor:
  sensorValue1 = adc.read(sensorPin1); // Blowing air
  sensorValue2 = sensorMin1 - sensorValue1; // Drawing in air

// Store previous values
  previousControllerValue1 = controllerValue1;
  previousControllerValue2 = controllerValue2;
  
// Usable range limits for sensor up/down
  sensorLow1 = sensorMin1 + lowThreshold1;
  sensorLow2 = sensorMin2 + lowThreshold2;

  sensorHigh1 = sensorLow1 + sensorRange1 - highThreshold1;
  sensorHigh2 = min(sensorMin1,sensorRange2) - highThreshold2;

// Convert internal values to output range (0..127 for controllers/aftertouch 0..+/-8191 for Pitchbend Up/Down) using the curves defined in "in" and "out" tables.
  controllerValue1 = map(mapToCurve(constrain(sensorValue1,sensorLow1,sensorHigh1),sensorLow1,sensorHigh1,in1,out1,sizeof(in1)/sizeof(int)),sensorLow1,sensorHigh1,0,outputRange1);
  controllerValue2 = map(mapToCurve(constrain(sensorValue2,sensorLow2,sensorHigh2),sensorLow2,sensorHigh2,in2,out2,sizeof(in2)/sizeof(int)),sensorLow2,sensorHigh2,0,outputRange2);

// Send MIDI messages  
  if (controllerValue1 != previousControllerValue1) sendSensorOutput(controllerNumber1, controllerValue1, controllerChannel1);
  if (controllerValue2 != previousControllerValue2) sendSensorOutput(controllerNumber2, controllerValue2, controllerChannel2);

// Debug

#ifdef DEBUG
// Sensor (input) values (uncomment for debug)
//  Serial.print (sensorValue1);
//  Serial.print (",");
//  Serial.print (sensorValue2);
//  Serial.print (",");
  
// Controller (output) values
  Serial.print (controllerValue1);
  Serial.print (",");
  Serial.println (controllerValue2);
#endif
  
// stop the program for for <refreshCycle> milliseconds:
  delay(refreshCycle);
}

// Function used to send MIDI messages according to controller number
void sendSensorOutput (int number, int value, int channel) {
  //if (number < 128) MIDI.sendControlChange(number, value, channel);
  //else if (number == 128) MIDI.sendAfterTouch(value, channel);
  //else if (number == 129) MIDI.sendPitchBend(value, channel);
  //else if (number == 130) MIDI.sendPitchBend(-value, channel);
  //***********************************************************************
  if (number < 128) usbMIDI.sendControlChange(number, value, channel, 0);
  else if (number == 128) usbMIDI.sendAfterTouch(value, channel, 0);
  else if (number == 129) usbMIDI.sendPitchBend(value, channel, 0);
  else if (number == 130) usbMIDI.sendPitchBend(-value, channel, 0);
  // Note the 0 defines which Cable to send on.
  //***********************************************************************
}

// Function used to determine the range of a specific controller. This is due to the fact pitch bend has a larger range than regular controllers.
int outputRange (int number) {
  if (number > 128) return 8191;
  else return 127;
}

// Modified multiMap function used to create curves. Original by Rob Tillaart.
int mapToCurve(int val, int sensorLow, int sensorHigh, int* _in, int* _out, uint8_t size)
{
  // take care the value is within range
  // val = constrain(val, _in[0], _in[size-1]);
  if (val <= map(_in[0],0,127,sensorLow,sensorHigh)) return map(_out[0],0,127,sensorLow,sensorHigh);
  if (val >= map(_in[size-1],0,127,sensorLow,sensorHigh)) return map(_out[size-1],0,127,sensorLow,sensorHigh);

  // search right interval
  uint8_t pos = 1;  // _in[0] already tested
  while(val > map(_in[pos],0,127,sensorLow,sensorHigh)) pos++;

  // adjusting range from ..127 to sensor range
  int inPos = map(_in[pos],0,127,sensorLow,sensorHigh);
  int outPos = map(_out[pos],0,127,sensorLow,sensorHigh);
  int inPrv = map(_in[pos-1],0,127,sensorLow,sensorHigh);
  int outPrv = map(_out[pos-1],0,127,sensorLow,sensorHigh);

  
  // this will handle all exact "points" in the _in array
  if (val == inPos) return outPos;
  
  // interpolate in the right segment for the rest
  return ((long)val - (long)inPrv) * ((long)outPos - (long)outPrv) / ((long)inPos - (long)inPrv) + (long)outPrv;
}
 
Works Perfectly! now i don't pass out when blowing. I did change the sensor range to 1500 as it was too easy. Thank you!!
 
Back
Top