Skip to content
Mark Harris edited this page May 9, 2021 · 1 revision

Goal of EigenLite

the idea was to make it simple to create apps that support the Eigenharp.

How to start

the test program thats included really shows all that is necessary.

https://github.com/TheTechnobear/EigenLite/blob/master/tests/eigenapitest.cpp

Lets look at the test program to see how it works

lets strip that down (remove 'details') , to demonstrate, and point out some potential 'gotchas'

void* process(void* arg) {
    EigenApi::Eigenharp *pE = static_cast<EigenApi::Eigenharp*>(arg);
    while(keepRunning) {
        pE->process();
    }
    return nullptr;
}


int main(int ac, char **av)
{
    EigenApi::Eigenharp myD("./");
    myD.setPollTime(100);
    myD.addCallback(new PrinterCallback(myD));
    if(!myD.start())
    {
		std::cout  << "unable to start EigenLite";
		return -1;
    }
    std::thread t=std::thread(process, &myD);
    t.join();
    myD.stop();
    return 0;
}

lets look at each line, and see what it does

EigenApi::Eigenharp myD("./"); creates main object , the PATH is important since this is where it looks to find the firmware that is uploaded to the basestation/pico this is not done till 'start'

myD.setPollTime(100); lets come back to this, when we talk about processing messages !

myD.start() attempts to start the eigenharp, including uploading firmware, and then starts polling loop.

myD.addCallback(new PrinterCallback(myD)); all messages from the Eigenharp come back as callbacks onto an object provided more details below

myD.stop(); stop polling eigenharp, and detach it.

EigenApi::Callback

these are the events that are generated by the Eigenharp I should point out these are very close to what the eigenharp generates (remember EigenLite aims to be as close as possible to the hardware)

virtual void device(const char* dev, DeviceType dt, int rows, int cols, int ribbons, int pedals) when we connect, this gives us information about what is connected.

virtual void key(const char* dev, unsigned long long t, unsigned course, unsigned key, bool a, unsigned p, int r, int y)

an event for all keys pressed, we get these continuously for the main keys a = active - so is it on/off

virtual void breath(const char* dev, unsigned long long t, unsigned val) breath events, continuous

virtual void strip(const char* dev, unsigned long long t, unsigned strip, unsigned val) strip events, continuous

virtual void pedal(const char* dev, unsigned long long t, unsigned pedal, unsigned val) pedal events

I think most of this is pretty obvious, best way to make sense of the data is run the test app. the important/surprising thing is, that key course - this has nothing to do with the physical layout ! you will find all the main keys in ONE course,

I need to re-check this , but I think (iirc)

  • alpha has two courses , one for main keys, onr=e for percussion
  • pico has two courses , one for main keys, one for round on/off keys
  • tau has three courses , one for main keys, one for percussion, and one for round on/off keys

(this is likely to be wrong, but its something like this ;) )

Threading - Polling/Callbacks

ok, so this is very important! we have to remember the Eigenharp is capable of generating a huge number of events over a sustained period... also its MAIN purpose is to integrate these into an audio stream at AUDIO RATE.

yes, midi/osc is useful - but this was not its design - Eigenlabs will argue midi/osc is too slow, and use lose alot of the subtle movements.

this is NOT to put you off, I think midi/osc is great BUT this helps explain the approach taken!

note: the approach taken here is same as EigenD.

when we start the Eigenharp it immediately starts a number of threads which are doing the USB communications these are managed to automatically. they are run at realtime priority, as its is important they are dont miss events - if you run on a platform with not enough cpu/cores. these will probably get starved, and mean you will get issues!

so where do these events go? they cannot go straight to your callback... since that could potentially stall the usb comms, and also you would have to be very careful with thread safety.

so, instead they get placed onto a lockfree queue. from this queue, we can take the events and call your callback (you can have more than one callback)

so that your program has to do, is call poll() regularly this will take the events of this internal queue, and then send them to your callback(s) objects.

this is what the test app does you can see if created a thread, which it then calls poll on regularly

the argument on poll, is really just convienice, it will basically wait for this time (in milliseconds) for events. zero means just process events and return immediately (this is what you would use if in the main audio thread!)

this also answers the other crucial question... what context do the callbacks get called in? they get called in the same thread as you calling poll() in , this greatly simplifies your code, since you are in control of this thread.

however , remember the Eigenharp is generating a lot of events, with realtime priority, so you must service with poll() regularly, if you don't you will fall behind, and the lockless queue will start dropping events.

on platforms with limited processing power (like the rPI) with other audio processing going on, I sometimes raise this poll thread to realtime priority, to ensure it can compete with the audio threads and the usb hardware threads!. on a desktop its not usually an issue - but if you start missing events, keep this in mind.

Logging

there is a logging api, which allows you to get error messages, by default i use an in built one that goes to stderr (?). but you can override (intended for gui apps, which may have a console window)

note: this logging is potentially called from different threads so do NOT do anything that is not thread safe, and never block it.