Reading sensors

Hi,

 

I am planing to create a new app, this time for Android and iOS where I need to have access to the sensors

like accerleration and deviation. Since I am familar with juce I would like to stay with the frame work but don't know

whether I can achive such things using juce?

 I read about Eclipse but again, why should I change if I could do the same with juce?

 

Any help is highly appreciated!

Joerg

 

Eclipse is an IDE, JUCE is a framework. So, that comparison doesn't make sense. Having said that JUCE does not constrain you to only what is available thru it, you can also write platform specific code, so you should be able to do what you desire without any problems. If I were doing it, I would write some cross-platform classes to encapsulate my sensors/etc.

 

-cpr

Eclipse is an IDE, JUCE is a framework. So, that comparison doesn't make sense.

Thank you!

 

 If I were doing it, I would write some cross-platform classes to encapsulate my sensors/etc.

Well,  what is the goal of using juce? It provides cross-platform classes off the box. If I would have time and knowledge to write

cross-platform classes to create audio plugins, It could save me lots of money. But I neighter have the time nor the knowledge to 

do this and therefore I was rather interested in a bit more helpful information like "Yep, there is native juce sensor support underway"

or simply "No, but have a closer look at this library..." (Maybe some guys here were already faced with the same problem?)

If I have misunderstood your tone, sorry!

I think you did mis-understand my tone. It was simple meant to be clear and accurate. And, well, encouraging. :)

'What is the goal of using JUCE?' That's the whole point, JUCE provides you with all of the major areas of API abstractions, so you don't have to write any native code for those areas. You will only have to write native code for the naive API's you want to access. And lastly, I was just encouraging you do encapsulate that native code in some classes, so that you don't have naive code with the app itself.

And, it may still be true that someone else has done this work, and would be willing to share it with you.

-cpr

Okay then, apologize for misunderstanding your tone :-)

I found out that QT supports sensors but since I would like to stay with juce I will wait

and hope somebody will come up with some ideas.

 

Thanks!

Joerg

Adding some classes for sensors has been on my to-do-list for a while.. It's not hard to do - I guess the only real reason I've not done it yet is just because I've not had any pressing need for it in any of my own projects!

(But I'm sure I've seen it mentioned on here before, so there could be people who've already got some code you could use)

Glad to hear that it wouldn't be hard to get such classes into juce.

I just need to find a way of requesting this for Traction to speed things up :-)

Meanwhile I will hope for the mercy of people may having this done already

and would share the code.

Thanks!

 

i have android accelerometer code, i keep trying to give to Juce :-)

 

 

Damn.. yes, we must sort that out!

Hi hugh,

 

this is great news. But I doubt that jules will be able to put this in juce quickly (just because of his lack of time)

and I am wondering whether you would share the code with me?

 

Thanks!

 

Hi,

 

Yes, i will try to sort something out. let me try to extract those bits and post them here. 

 

here's code for Android and iOS to read the accelerometer. Unfortunately, i implemented both slightly different but the values returned are compatible. I'll cover the iOS first, because it's smaller and this way might be the preferred way to put this into Juce.

This way adds a function `virtual void getGravity(float& x, float& y, float& z)'  to ComponentPeer like so,

 

class JUCE_API  ComponentPeer
{
public:
...
   virtual void getGravity(float& x, float& y, float& z) 
    {
        x = 0;
        y = 0;
        z = 0;
    }

...

};


here's a patch for modules\juce_gui_basics\native\juce_ios_UIViewComponentPeer.mm

 

--- v:\sw\juce\modules\juce_gui_basics\native\juce_ios_UIViewComponentPeer.mm    2013-11-07 15:48:26.000000000 -0000
+++ c:\sw\juce\modules\juce_gui_basics\native\juce_ios_UIViewComponentPeer.mm    2013-11-16 18:29:57.000000000 -0000
@@ -95,27 +95,32 @@
 - (BOOL) canBecomeFirstResponder;
 
 - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text;
 @end
 
 //==============================================================================
-@interface JuceUIViewController : UIViewController
+@interface JuceUIViewController : UIViewController<UIAccelerometerDelegate>
 {
+    UIAccelerometer* accelerometer;
+@public
+    float accel[3];
 }
 
 - (NSUInteger) supportedInterfaceOrientations;
 - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation;
 - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration;
 - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation;
+- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration;
 
 - (void) viewDidLoad;
 - (void) viewWillAppear: (BOOL) animated;
 - (void) viewDidAppear: (BOOL) animated;
 - (void) viewWillLayoutSubviews;
 - (void) viewDidLayoutSubviews;
 @end
+
 
 //==============================================================================
 @interface JuceUIWindow : UIWindow
 {
 @private
     UIViewComponentPeer* owner;
@@ -248,12 +253,42 @@
 
         return r;
     }
 
     MultiTouchMapper<UITouch*> currentTouches;
 
+
+    void getGravity(float& x, float& y, float& z) 
+    {
+        Desktop& desktop = Desktop::getInstance();
+        Desktop::DisplayOrientation rot = desktop.getCurrentOrientation();
+
+
+        z = controller->accel[2];
+
+        switch (rot)
+        {
+        case Desktop::upsideDown:
+            x = -controller->accel[0];
+            y = -controller->accel[1];
+            break;
+        case Desktop::rotatedClockwise:
+            x = controller->accel[1];
+            y = -controller->accel[0];
+            break;
+        case Desktop::rotatedAntiClockwise:
+            x = -controller->accel[1];
+            y = controller->accel[0];
+            break;
+        default:
+            x = controller->accel[0];
+            y = controller->accel[1];
+        }
+
+    }
+
 private:
     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
 
     class AsyncRepaintMessage  : public CallbackMessage
     {
     public:
@@ -310,12 +345,17 @@
 }
 
 - (void) viewDidLoad
 {
     JuceUIView* juceView = (JuceUIView*) [self view];
     jassert (juceView != nil && juceView->owner != nullptr);
+    
+    accelerometer = [UIAccelerometer sharedAccelerometer];
+    accelerometer.updateInterval = 0.2;
+    accelerometer.delegate = self;
+
     juceView->owner->updateTransformAndScreenBounds();
 }
 
 - (void) viewWillAppear: (BOOL) animated
 {
     (void) animated;
@@ -335,12 +375,23 @@
 
 - (void) viewDidLayoutSubviews
 {
     [self viewDidLoad];
 }
 
+- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
+{
+    //float f = 0.2f;
+
+    // apply low pass filter (or not!)
+    accel[0] = acceleration.x; //  * f + accel[0] * (1 - f);
+    accel[1] = acceleration.y; // * f + accel[1] * (1 - f);
+    accel[2] = acceleration.z; // * f + accel[2] * (1 - f);
+
+}
+
 @end
 

 

now the Android version is a bit more complicated. most of the changes are in the Java activity. I added a new class to Juce called `SystemFeatures' because i didnt really know where it should be put. Like i said before, it might be better to add this the same as above in ComponentPeer (although, in fact, it's global which is why i didnt put it in ComponentPeer for Android).

The code is all bits and pieces, so i can't just post a patch, instead i'll talk through the changes to juce.

(1) need new entry in modules\juce_core\native\juce_android_JNIHelpers.h    

 METHOD (getTiltAngleX,          "getTiltAngleX",        "()F") \
 METHOD (getTiltAngleY,          "getTiltAngleY",        "()F") \

(2) Then I put calls to this somewhere global in Juce, like this:


float SystemFeatures::getTiltAngleX()
{
    return android.activity.callFloatMethod (JuceAppActivity.getTiltAngleX);
}

float SystemFeatures::getTiltAngleY()
{
    return android.activity.callFloatMethod (JuceAppActivity.getTiltAngleY);
}


(3) Then the hard bit is all the code to change the Java activity.


import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public final class JuceAppActivity   extends Activity
...
   @Override
    public final void onCreate (Bundle savedInstanceState)
    {
        super.onCreate (savedInstanceState);
        onCreateVerified();
    }
    private void onCreateVerified()
    {
            
        sensors = new Sensors (this);
        viewHolder = new ViewHolder (this);
    
        setContentView (viewHolder);
        setVolumeControlStream (AudioManager.STREAM_MUSIC);
    
    }
  @Override
    protected final void onPause()
    {
        if (DBG) Log.d(TAG, "onPause");
        super.onPause();
        if (viewHolder != null)
        {
            suspendApp();
            // Android will pause GL thread
            viewHolder.onPause();
            
            sensors.onPause();
        }
    }
  @Override
    protected final void onResume()
    {
        if (DBG) Log.d(TAG, "onResume");
        super.onResume(); // this must go first
      
        if (viewHolder != null)
        {
            sensors.onResume();
            // android will resume GL thread
            viewHolder.onResume();
            resumeApp();
        }
    }
    public interface Pausable
    {
        void onPause();
        void onResume();
       
    }

   // class to read sensors
    private Sensors sensors;
    public final class Sensors implements SensorEventListener, Pausable
    {
        public Sensors(Context ctx)
        {
            context = ctx;
            mgr = (SensorManager) context.getSystemService(SENSOR_SERVICE);
            //accelerometer = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            gravitySensor = mgr.getDefaultSensor(Sensor.TYPE_GRAVITY);
            // reg early in-case `resume' not called
            onResume();
        }
        @Override
        public final void onResume()
        {
            if (gravitySensor != null && !_reg)
            {
                mgr.registerListener(this, gravitySensor,
                                     SensorManager.SENSOR_DELAY_UI);
                _reg = true;
            }
        }
        @Override
        public final void onPause()
        {
            if (_reg)
            {
                mgr.unregisterListener(this, gravitySensor);
                _reg = false;
            }
        }

       
    public void onAccuracyChanged(Sensor sensor, int accuracy) 
        {
        // ignore
    }
    
        public void onSensorChanged(SensorEvent event) 
        {
            switch(event.sensor.getType())
            {
            case Sensor.TYPE_GRAVITY:
                
                for(int i=0; i<3; i++) 
                    gravity[i] = event.values[i];
                
                // cos(ang) -> angleX
                angleX = gravity[1]/SensorManager.GRAVITY_EARTH;
                if(angleX > 1.0) angleX = 1.0;
                else if(angleX < -1.0) angleX = -1.0;
                //angle = Math.toDegrees(Math.acos(ratio));
                if(gravity[2] < 0) angleX = -angleX;
                angleY = gravity[0]/SensorManager.GRAVITY_EARTH;
                if(angleY > 1.0) angleY = 1.0;
                else if(angleY < -1.0) angleY = -1.0;
                break;
            }
        }
        boolean                 _reg;
        public double          angleX;
        public double          angleY;
        
        Context                 context;
        private SensorManager   mgr;
    private float[]         gravity = new float[3];
        private Sensor          gravitySensor;
    };
    public final float getTiltAngleX()
    {
        // return sin(ang), adjusted for -90.. 0.. 90
        return -(float)sensors.angleX;
    }
    public final float getTiltAngleY()
    {
        // return sin(angle)
        // where angle is afjusted for -90 .. 0 .. +90
        // with 0 in the flat position
        return (float)sensors.angleY;
    }

...
  final class ViewHolder  extends ViewGroup implements Pausable
...
    @Override
        public final void onPause()
        {
            for (int i = getChildCount(); --i >= 0;)
            {
                View v = getChildAt (i);
                if (v instanceof Pausable)
                    ((Pausable) v).onPause();
            }
        }
        @Override
        public final void onResume()
        {
            for (int i = getChildCount(); --i >= 0;)
            {
                View v = getChildAt (i);
                if (v instanceof Pausable)
                    ((Pausable) v).onResume();
             }
         }
...
   public final class ComponentPeerView extends ViewGroup
        implements View.OnFocusChangeListener, Pausable
...
  @Override
        public final void onPause()
        {
            for (int i = getChildCount(); --i >= 0;)
            {
                View v = getChildAt (i);
                if (v instanceof Pausable)
                    ((Pausable) v).onPause();
            }
        }
        @Override
        public final void onResume()
        {
            for (int i = getChildCount(); --i >= 0;)
            {
                View v = getChildAt (i);
                if (v instanceof Pausable)
                    ((Pausable) v).onResume();
            }
        }
...


 public final class OpenGLView   extends GLSurfaceView
                        implements GLSurfaceView.Renderer, Pausable
                                   
...

     @Override
        public void onResume() 
        {
            if (DBG) Log.d(TAG, "GLView.onResume");
            
            super.onResume(); // must be last
        }
        @Override
        public void onPause() 
        {
            if (DBG) Log.d(TAG, "GLView.onPause");
            
            super.onPause();
        }

This includes my "Pausable" interface because everything needs to pause including sensors, viewholders, glviews and just about anyone. It's possible you can just take the sensor bit, although you'll have to be sure to pause during `onPause' and `onResume'. Here i pull all this together in one interface. Later the WebView needs to pause too (when that goes in).

 

hope this helps,

 

 

1 Like

That is great, thank you very much Hugh!

It will probably take some time to get my head arround this but it is a very good starting point.

Best

Joerg

Ok, just having a look through this stuff..

It looks like the iOS classes you used have been deprecated, and replaced by the CoreMotion framework.. It's been available since iOS 4, so probably a good idea to go directly to CoreMotion rather than implementing with the old SDK? Or is iOS 3 support still a big deal for people?

To get a cross-platform implementation, I guess the end-user function we want to expose would look something like:

class Accelerometer
{
    static Vector3D<float> getAcceleration();
};

?

But looking at the android stuff, what's the deal with only having an X and Y tilt? aren't there 3 coords like on iOS? If not, would it be equivalent to the iOS implementation if those tilt values were passed as the X and Y of a Vector3D, and the Z left at 0?

Actually, thinking about it, probably the cleanest API would be a class like this:

class Acceleration
{
    float x, y, z;

    static Acceleration getCurrent();
};

yes, `onSensorChanged' has a third value in `gravity' I just didnt add a function for it. By all means plumb this into your new interface, which will be a lot tidier than mine.

 

Hi Jules,

are you actually working on this? Just because then I would wait for your check in.

Thanks 

I had a quick look, but am a bit overloaded with tracktion stuff right now.

Would be good to get some input from people who've actually written some accelerometer code, as I'd like to get the design right, and not being familiar with the way each platform works, it'd be good to figure out what the classes should look like before implementing it.

I would add my vote to have a Sensors class added as well.

There are also APIs on Windows e.g. for "Location", so this could be more useful than just for mobile.

bump?