Skip to content

A simple delay effect for Android

March 7, 2012

Some of you might wonder how to use the code in my previous post to build processing applications. In this note, I will show you how. In fact, I have put in the work in designing the OpenSL module so it can be re-used. For this, you don’t have to touch it, only add it to your project.

You can follow the instructions on how to put the NDK project from the previous blog. Or you can just copy it and start modifying the code. What we need to do is to edit the opensl_example.c and opensl_example.h files. To this we will be adding some processing capabilities.

We will like to do an echo effect, based on a comb filter. So let’s build the infrastructure for it. We will create a simple class in C, based on a data structure (its data members) and some functions to manipulate it (its methods). Here it is, we can add it to the top of the C source file:

typedef struct delayline_t {
 float *delay; // delayline
 int size;     //  length  in samples
 int rp;       // read pointer
 float fdb;    // feedback amount
} DELAYLINE;

DELAYLINE *delayline_create(float delay, float fdb) {
 DELAYLINE *p = (DELAYLINE *)calloc(sizeof(DELAYLINE), 1);
 p->size = delay*SR;
 p->delay = calloc(sizeof(float), p->size);
 p->fdb = fdb > 0.f ? (fdb < 1.f ? fdb : 0.999999f) : 0.f ;
 return p;
}
void delayline_process(DELAYLINE *p,float *buffer,
                                           int size) {
 // process the delay, replacing the buffer
 float out, *delay = p->delay, fdb = p->fdb;
 int i, dsize = p->size, *rp = &(p->rp);
 for(i = 0; i < size; i++){
 out = delay[*rp];
 p->delay[(*rp)++] = buffer[i] + out*fdb;
 if(*rp == dsize) *rp = 0;
 buffer[i] = out;
 }
}

void *delayline_destroy(DELAYLINE *p){
 // free memory
if(p->delay != NULL) free(p);
if(p != NULL) free(p);
}

With this in place, all we need is to modify our main process to use the delayline. We will add a couple of arguments to the main processing function (and rename it main_process from start_process), so that from the Java app code, we can set the delay time and feedback amount:

void main_process(float delay, float fdb) {
 OPENSL_STREAM *p;
 int samps, i;
 float buffer[VECSAMPS];
 DELAYLINE *d;
 p = android_OpenAudioDevice(SR,1,1,BUFFERFRAMES);
 if(p == NULL) return;
 d = delayline_create(delay, fdb);
 if(d == NULL) {
  android_CloseAudioDevice(p);
  return;
 }

 on = 1;
 while(on) {
   samps = android_AudioIn(p,buffer,VECSAMPS);
   delayline_process(d,buffer,samps);
   android_AudioOut(p,buffer,samps);
 }
 android_CloseAudioDevice(p);
}

Now, the only two things that are left to do are: edit the opensl_example.h header file and substitute the start_process() by the main_process(float delay, float fdb) prototype,

#ifdef __cplusplus
extern "C" {
#endif
 void main_process(float delay, float fdb);
 void stop_process();
#ifdef __cplusplus
};
#endif

and modify the Java code to call this new function

t = new Thread() {
 public void run() {
 setPriority(Thread.MAX_PRIORITY);
 opensl_example.main_process(0.5f, 0.7f);
 }
 };

and you should be good to go!

Advertisements
11 Comments
  1. Well, it should not too difficult to define a Faust architecture for Android now, so that you can run Faust DSP…

  2. No, it shouldn’t. I guess it’s possible right away with the no-arch faust output, but it would be handy to have faust generating everything including the UI and project. No apple tax involved!

  3. Hello,
    Thanks for making this available. I am just having a wonderful time singing in harmony with my prevois self from two seconds ago into the headset mic of the phone 🙂
    I am sure I wouldn’t have made it so for in so little time without this great ressource. (This is my very first try with android audio)
    I found that your delayline_create function is a bit messed up. I could not get it to work.
    Here is my fixed version:

    DELAYLINE *delayline_create(float delaySecs, float fdb) {
    // allocate memory and set feedback parameter
    int size = delaySecs*SR;
    DELAYLINE *p = (DELAYLINE *)calloc(sizeof(DELAYLINE) + size * sizeof(float), 1);
    p->size = size;
    p->delay= (float*)(p+1);
    memset(p->delay, 0, sizeof(float)*p->size);
    p->fdb = fdb > 0.f ? (fdb rp = 0;
    return p;
    }

    Now I am only struggeling to get SWIG configured correctly so that it works fine with the eclipse NDK plugin.

    • yes, that looks like total bollocks, does it not? Thanks for pointing it out. I don’t know where my mind was when I wrote that down. Fixed.

  4. Romain permalink

    Hi Victor,

    First, thanks for all these great tutorials!
    I just have one short question: in the echo example you’re providing here, how would you dynamically control the parameters of the echo effect from the JAVA side of the app? For example, can you think about an easy way to use an Android SeekBar to control the delay duration. I tried several techniques, but none of them were really straightforward.

    Thanks for your help!

    Cheers,

    Romain

    • Hi Romain,

      to do that, you will need to create a C function in the NDK code (echo.c) and then wrap it with SWIG, This can be done simply by adding function prototypes to echo.h.
      Then these become available to the Java code. That bit should not be too difficult. The only thing you will probably need to watch out for is that the NDK code is running in its own thread, and you might need to protect the access to the variables you want to adjust.
      One thing you can do is that instead of having a function that encapsulates the whole process like in the example (main_process()),
      you could have a function that processes a single block of audio, and then you can call it in a loop in the java code. That way you
      can make changes to the processing state in between the calls. You could store the value of a seekBar in a variable and then
      update the processing parameter in between the calls.

      Also, if you want to change the delay duration, you will need to implement a variable delay rather than a fixed one.

      I hope that helps.

      • Romain permalink

        Hi Victor,

        Thanks so much for you quick answer!
        I already tried to do something where I have a function that processes a single block of audio and that is looped directly in the JAVA code. However, the main problem I have if I use such an approach is that I need to also call the android_OpenAudioDevice() and android_CloseAudioDevice() functions in the JAVA code in order to pass the OPENSL_STREAM variable returned by android_OpenAudioDevice() to android_AudioIn and android_AudioOut:

        a=android_OpenAudioDevice(…)
        while(on){
        compute_dsp_and_write_the_audio_block_to_audio_engine(a,…)
        }
        android_CloseAudioDevice(a)

        So in that case, I don’t really see how I can use swig to wrap android_OpenAudioDevice that returns a type OPENSL_STREAM…

        I’m really not experimented with SWIG and JNI so I have hard time to get a good idea of what you can and what you can’t do with these tools.

        Thanks again for your time!

        Cheers,

        Romain

      • I don’t think that should be a difficulty. SWIG will wrap the pointer into a Java class, and you should be able to use it in your program.
        If you want things to look more uniform, you can always make all your code into C++ classes and then wrap these. The C++ classes should translate
        more or less directly into Java classes and methods.

      • I had a quick look, and this is something you can do. Create an interface like this (echo.h):

        void *echo_init(float delay, float fdb);
        int echo_process(void *p);
        void echo_destroy(void *p);

        Implement these functions with an ECHO struct consisting of a pointer to a DELAYLINE and another to an OPENSL_STREAM, which you will fill appropriately. Pass this ECHO state as a void pointer between the three functions. When SWIG wraps this interface, it will create a SWIGTYPE_p_void class. In your Java code you create an object of that type, use the echo.echo_init() to assign to it, and then use it in the other methods.
        Pretty simple.

        If you find SWIGTYPE_p_void ugly, you can do the whole thing as a C++ class and that will be wrapped directly into a Java class.

      • Romain permalink

        Thanks Victor, I rewrote the whole thing in C++ and it indeed solved all my problems. I was able to run some Faust code instead of your “hand written” echo effect and it works great.

        Thanks again for your help!

      • Good to hear. I was wondering actually when FAUST was going to be thrown in the mix. Good luck with the project.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: