Accelerometer to midi cc with arduino

Hi all

I’m trying something which feels like I should start a bit smaller. But I’m shooting anyway.
I accidentally ordered an adafruit mma8451 accelerometer. Which has a serial output and not 3 analog outputs… So I couldn’t really replicate Sam’s lightsaber theremin.

So I hooked it up to an arduino and tried to get the info out of the sensor and send it as a midi cc message to the computer… But I’m not there yet. This is currently my obvious code that isn’t working.

#include <Adafruit_MMA8451.h>
#include <Adafruit_Sensor.h>

Adafruit_MMA8451 mma = Adafruit_MMA8451();

  const int axis = 3;
  int axislevel [axis] = {0, 0, 0};
  int axisinput [axis] = {mma.x, mma.y, mma.z};
  int midiChannel = 1;
  int midiCCnumbers [axis] = {16, 17, 18};
  int midiCCvalues [axis] = {0, 0, 0};
  
void setup(void) {
  Serial.begin(38400);

  if (! mma.begin()) {
    Serial.println("Couldnt start");
    while (1);
  }
  Serial.println("MMA8451 found!");
  
  mma.setRange(MMA8451_RANGE_2_G);
  
  Serial.print("Range = "); Serial.print(2 << mma.getRange());  
  Serial.println("G");
  
  
  
}
void loop() {
  for (int i = 0; i < axis; i++)
     {
        midiCCvalues[i] = floor(mma.read((axisinput[i]+8192)/129));  
        sendCC(midiChannel, midiCCnumbers[i], midiCCvalues[i]);
        delay(10);
     }
}
 
void sendCC(uint8_t channel, uint8_t control, uint8_t value) {
  Serial.write(0xB0 | (channel & 0xf));
  Serial.write(control & 0x7f);
  Serial.write(value & 0x7f);
  
}

This part is getting in my way… (I think)

  for (int i = 0; i < axis; i++)
     {
        midiCCvalues[i] = floor(mma.read((axisinput[i]+8192)/129));
        sendCC(midiChannel, midiCCnumbers[i], midiCCvalues[i]);
        delay(10);
     }

The mma.read is 14 bit that needs to be converted to 8 bit cc… So -8192 to 8192 needs to become 0-127…

So it’s the value that’s read +8192 /129

However I have no idea how to code that! Someone suggested +MaxAnalogValue/mididivider… Which I don’t get. And it doesn’t work.

It’s always giving me trouble with this phrase:
midiCCvalues[i] = floor(mma.read((axisinput[i]+8192)/129));

So I need three values x,y and z. That needs to be converted to 8 bit…

Without the midi, I can read all the values just fine… in 14-bit.

Ow yeah… In my cv to midi… It was this line:

cvLevels[i] = floor(analogRead(analogInputs[i])/8);

Which is 10 bit to 8 bit.

Maybe I’ll give up on this. But not yet. I want my musical lightsaber!

2 Likes

Sounds like Arduino’s map is what you need:

out = map(in, -8192, 8192, 0, 127);

More here: map() - Arduino Reference

3 Likes

Based on what I gather from this page:

It looks like you need something like this (not tested)

mma.read(); // Read X, Y and Z values all at once.
midiCCvalueX = (mma.x + 8192) >> 7 ; // Get signed 14-bit X value, shift it from -8192..8191 to 0..16383 to get unsigned 14-bit value, and drop 7 least significant bits to get unsigned 7-bit value.
3 Likes

With respect to this:

cvLevels[i] = floor(analogRead(analogInputs[i])/8);

there is a very easy way to reduce the numeric value of the ADC to the midi 7 bit domain: shift the bits a couple of positions to the right. I use something like this to adapt ADC input to a midi (1 byte) value:

int map2midi(int value) {
// value is read from a 10 ADC…
// Convert it to a 7 bit value by shifting the bits 3 positions to the right;
return (value >> 3);
}

Dividing/multiplying by a power of 2 is often translated by the compiler to a bit shift.

So you would call the map function like this:

cvLevels[i] = map2midi(analogRead(analogInputs[i]));
2 Likes

Thanks everyone! Getting a lot closer!

First code that actually compiled and uploaded…

Although I’m getting bogus readings and absolutely no effect from the sensor…

Tried:

midiCCvalues [i] = ((mma.x) + 8192)/129 ;

and

midiCCvalues [i] = ((mma.x) + 8192) >> 7;

It’s really weird.
Hairless midi constantly detects program changes on ch1. Always the same numbers. But I’m sending it out to a different channel? Even when changing it…

Just going to try it in ableton for the sake of it… To see what happens.

Nit: 2^7 is 128, not 129 (if you change that, the compiler will generate the same code (*) for both cases).

I suggest printing decimal values to the serial terminal output and looking at them in the serial monitor, instead of generating binary MIDI output, until you’ve verified that you have the right data.

(*) there may be a slight difference if you use signed integers, due to truncation rules.

1 Like

Correct. But than you get 0-128. So you’re max is outside the midi-scope of 127. That’s why I went with 129… so you get 0-127. Which is actually 128 digits?

False thinking? Or is 0, 1. Like the midi channels where midi channel 0 is channel 1.

The thing you’re dividing has an integral number of bits, so the “unsigned” range is 0 to 2^(bits)-1, e.g. for 14 bits you have 0 to 2^14−1 = 16383. If you ask your calculator, 16383/128 is indeed 127.99 but this is integer arithmetic so it’s truncated towards zero, and you get 127 (another way to see this is that you’re ignoring the lowest 7 bits; if x>>7 is correct, so is x/128. The compiler turns them both into exactly the same thing, so that better be the case :smiley:)

(I still think you should use map until you have things working, leaving any optimizations for later, but that’s me :grinning:).

2 Likes

Will do! Tomorrow is proper testday… I hope… Because curveballs are always present.

1 Like

If I remember (it’s been a while) I found the MIDI code section of nuts and volts channel really useful. Especially matters like precalculation to save arduino from maths on the fly.

2 Likes

Update: Ok… I came as far as being able to get the sensor to give values between 0-127… Great… Now to get them to properly send via midi…

void loop() {
  // Read the 'raw' data in 14-bit counts
  mma.read();
  
 mma.x = map(mma.x, -6000, 8192, 0, 127);

 Serial.print("X:\t"); Serial.print(mma.x);

I used -6000, because I noticed the sensor didn’t really read that low… So I adjusted the scope a bit.

2 Likes

Hooray! Got the x-axis to be able to output a midi cc message!
Simplified the code.

#include <Wire.h>
#include <Adafruit_MMA8451.h>
#include <Adafruit_Sensor.h>


Adafruit_MMA8451 mma = Adafruit_MMA8451();


void setup(void) {
  Serial.begin(31200);
  Serial.println("Adafruit MMA8451 test!");
  

  if (! mma.begin()) {
    Serial.println("Couldnt start");
    while (1);
  }
  Serial.println("MMA8451 found!");
  
  mma.setRange(MMA8451_RANGE_2_G);
  
  Serial.print("Range = "); Serial.print(2 << mma.getRange());  
  Serial.println("G");
  
}

void loop() {
  // Read the 'raw' data in 14-bit counts
  mma.read();
  
 mma.x = map(mma.x, -6000, 8192, 0, 127);

 Serial.print("X:\t"); Serial.print(mma.x);
    
      sendCC(0xB0, 16, mma.x);
      
      delay(20);
    }   
 
  
void sendCC(uint8_t channel, uint8_t control, uint8_t value) {
  Serial.write(0xB0 | (channel & 0xf));
  Serial.write(control & 0x7f);
  Serial.write(value & 0x7f);
}

Next step is getting the Y-axis and Z-axis in the code…

Also a question, because I have this problem with my cv to cc module as well… It’s constantly picking up midi signals… So it would be best to just send a midi message when a value changes… Because it’s hard to map your midi cc that way… It’s kinda hoping you got the right cc number.

1 Like

For anyone reading this and having a similar issue…

I resolved the midi mapping in ableton… By manually mapping them. Which is way harder than it should be.

Using a m4l device called midimapper.
and loopbe1 a virtual midi driver.

Making a midi track, with midimapper. Which outputs the midi message to loopbe1. Then setup ableton to have midi input from loopbe1.

So I’m not changing the module, but changing the way I midi map…

Fieuw! Probably going to install a switch if I want to map anything else, so I don’t constantly have to unplug the module.

edit: Full finished code with all axis working!

#include <Wire.h>
#include <Adafruit_MMA8451.h>
#include <Adafruit_Sensor.h>

Adafruit_MMA8451 mma = Adafruit_MMA8451();

void setup(void) {
  Serial.begin(31200);
  Serial.println("Adafruit MMA8451 test!");

  if (! mma.begin()) {
    Serial.println("Couldnt start");
    while (1);
  }
  Serial.println("MMA8451 found!");
  
  mma.setRange(MMA8451_RANGE_2_G);
  
  Serial.print("Range = "); Serial.print(2 << mma.getRange());  
  Serial.println("G");
  
}

void loop() {
  // Read the 'raw' data in 14-bit counts
  mma.read();
 
 mma.x = map(mma.x, -6000, 8192, 0, 127);
 mma.y = map(mma.y, -6000, 8192, 0, 127);
 mma.z = map(mma.z, -6000, 8192, 0, 127);
 
 Serial.print("X:\t"); Serial.print(mma.x);
 Serial.print("Y:\t"); Serial.print(mma.y);
 Serial.print("Z:\t"); Serial.print(mma.z);
 
      sendCC(0xB0, 16, mma.x);
      sendCC(0xB0, 17, mma.y);
      sendCC(0xB0, 18, mma.z);
      
      delay(30);
    }   
 
  
void sendCC(uint8_t channel, uint8_t control, uint8_t value) {
  Serial.write(0xB0 | (channel & 0xf));
  Serial.write(control & 0x7f);
  Serial.write(value & 0x7f);
}
2 Likes

A bit off topic maybe, but you seem to know your way around Max 4 Live. How does the parameter update mechanisme work in Max 4 Live?

I want to change the controller number in a midi message to another number but keep the controller value. So e.g.I want to change 20 123 into 19 123. I used an unpack i i object, only took the right hand output and put that in a pack object as the second input (on the first I put 19). I then used a midiparse to convert that back into a midi message. This seems to work, but when the controller value ( in this case 123 ) is changed e.g. by a fader on an external device, then the value 123 stays put and only changes when I change the controller number (H.L. 19) as well. Obviously I want the controller value to change continuously as it is changed by e.g. an external device. So it seems that I am missing some sort of triggering in my setup. You can see a pic of the patch here. It tries to do a bit more than I described, in that it also allows to change the midi channel, but when that is changed that also is not visible in the output until the controller number is changed.

Edit: @Jokke I found the solution to the problem. Apparently the pack object will only pack data if it either receives a new value on its left most input, or receives a ‘bang’ on that input. A ‘bang’ is max 4 live’s term for a trigger. So adding an ‘trigger’ object that provides a ‘bang’ for every new controller value it receives will do the trick. See the ‘t b f’ object in the following screen dump. This is the patch after some more development. It will now pass all other midi data unchanged and I left out the possibility to change the channel because any sequencer can do that for you anyway.

1 Like

Wish I could help, but m4l is like arduino for me. Something I won’t touch, because it would lead me too far…

So maybe I’ll come back to you in about a month.

Edit: spelling

Pure Data running on pretty much anything from an old phone to a raspberry pi can be a marvelous MIDI tool especially with audio and cv thrown into the mix. There are many examples to literally copy into the software, oh and it’s free. Enjoy

2 Likes