~hecanjog/pippi

a06042bb2633d6da757d990e6212129ead982ea6 — Erik Schoster 2 months ago 93c34c6
Relay internal messages to python and try to handle everything else internally
M Makefile => Makefile +1 -1
@@ 115,4 115,4 @@ install:
	./venv/bin/python setup.py develop

build:
	python setup.py develop
	./venv/bin/python setup.py develop

M astrid/orc/simple.py => astrid/orc/simple.py +4 -1
@@ 1,5 1,8 @@
from pippi import dsp, oscs
from pippi import dsp, oscs, renderer

def play(ctx):
    ctx.log('Rendering simple tone!')
    yield oscs.SineOsc(freq=dsp.rand(200, 300), amp=1).play(10).env('pluckout')

if __name__ == '__main__':
    renderer.run_forever(__file__)

M astrid/src/astrid.c => astrid/src/astrid.c +142 -76
@@ 1539,16 1539,13 @@ lpbuffer_t * deserialize_buffer(char * buffer_code, lpmsg_t * msg) {
 * ******/
int send_play_message(lpmsg_t msg) {
    mqd_t mqd;
    ssize_t qname_length;
    char qname[LPMAXQNAME] = {0};
    char qname[NAME_MAX] = {0};
    struct mq_attr attr;

    attr.mq_maxmsg = ASTRID_MQ_MAXMSG;
    attr.mq_msgsize = sizeof(lpmsg_t);

    qname_length = snprintf(NULL, 0, "%s-%s", LPPLAYQ, msg.instrument_name) + 1;
    qname_length = (LPMAXQNAME >= qname_length) ? LPMAXQNAME : qname_length;
    snprintf(qname, qname_length, "%s-%s", LPPLAYQ, msg.instrument_name);
    snprintf(qname, NAME_MAX, "/%s-msgq", msg.instrument_name);

    if((mqd = mq_open(qname, O_CREAT | O_WRONLY, LPIPC_PERMS, &attr)) == (mqd_t) -1) {
        syslog(LOG_ERR, "send_play_message mq_open: Error opening message queue. Error: %s\n", strerror(errno));


@@ 1670,24 1667,23 @@ int astrid_msgq_close(mqd_t mqd) {
    return 0;
}

lpmsg_t astrid_msgq_read(mqd_t mqd) {
    lpmsg_t msg = {0};
int astrid_msgq_read(mqd_t mqd, lpmsg_t * msg) {
    char * msgp;
    ssize_t read_result;
    unsigned int msg_priority = 0;

    syslog(LOG_DEBUG, "Reading from msgq mqd:%d\n", mqd);
    syslog(LOG_DEBUG, "Reading from msg.msg:%s\n", msg.msg);
    syslog(LOG_DEBUG, "Reading from msg.instrument_name:%s\n", msg.instrument_name);
    msgp = (char *)(&msg);
    syslog(LOG_DEBUG, "Reading from msg.instrument_name:%s\n", msg->instrument_name);
    msgp = (char *)msg;

    syslog(LOG_DEBUG, "calling mq_receive:%d\n", mqd);
    if((read_result = mq_receive(mqd, msgp, sizeof(lpmsg_t), &msg_priority)) < 0) {
        syslog(LOG_ERR, "astrid_msgq_read mq_receive: Error reading message. (Got %ld bytes) Error: %s\n", read_result, strerror(errno));
        return -1;
    }

    syslog(LOG_DEBUG, "done calling mq_receive:%d\n", mqd);
    return msg;
    syslog(LOG_DEBUG, "msg.msg is now:%s\n", msg->msg);

    return 0;
}

int astrid_get_playback_device_id() {


@@ 2402,8 2398,6 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
    float * input_channels[instrument->channels];
    size_t i;
    int c;
    syslog(LOG_DEBUG, "inside the jack callback. %s\n", instrument->name);
    syslog(LOG_DEBUG, "inside the jack callback. init: %d\n", instrument->has_been_initialized);

    if(!instrument->is_running) return 0;



@@ 2561,17 2555,46 @@ void * instrument_seq_pq(void * arg) {
    return 0;
}

int relay_message_to_seq(lpinstrument_t * instrument) {
    int pqnode_index=0;
    lpmsgpq_node_t * d;
    double seq_delay, now=0;

    syslog(LOG_DEBUG, "MSG: schedule\n");

    d = &instrument->pqnodes[pqnode_index];
    memcpy(&d->msg, &instrument->msg, sizeof(lpmsg_t));
    pqnode_index += 1;
    while(pqnode_index >= NUM_NODES) pqnode_index -= NUM_NODES;

    if(lpscheduler_get_now_seconds(&now) < 0) {
        syslog(LOG_CRIT, "Error getting now in seq relay\n");
        return -1;
    }

    /* Hold on to the message as long as possible while still 
     * trying to leave some time for processing before the target deadline */
    seq_delay = instrument->msg.scheduled - (instrument->msg.max_processing_time * 2);
    d->timestamp = instrument->msg.initiated + seq_delay;

    if(pqueue_insert(instrument->msgpq, (void *)d) < 0) {
        syslog(LOG_ERR, "Error while inserting message into pq during msgq loop: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

void * instrument_message_thread(void * arg) {
    lpmsg_t bufmsg = {0}; // the message serialized along with the async buffer...
    lpbuffer_t * buf; // async renders: FIXME, do renders in a thread if possible... or fork out early for the python interpreter maybe?
    double processing_time_so_far, onset_delay_in_seconds, seq_delay, now=0;
    int pqnode_index=0;
    lpmsgpq_node_t * d;
    double processing_time_so_far, onset_delay_in_seconds, now=0;
    lpinstrument_t * instrument = (lpinstrument_t *)arg;

    instrument->is_waiting = 1;
    while(instrument->is_running) {
        if(astrid_playq_read(instrument->playqd, &instrument->msg) == (mqd_t) -1) {
        instrument->msg.scheduled = 0;
        if(astrid_msgq_read(instrument->msgq, &instrument->msg) == (mqd_t) -1) {
            syslog(LOG_ERR, "%s renderer: Could not read message from playq. Error: (%d) %s\n", instrument->name, errno, strerror(errno));
            usleep((useconds_t)10000);
            return NULL;


@@ 2581,28 2604,39 @@ void * instrument_message_thread(void * arg) {
        syslog(LOG_DEBUG, "MSG: %d (msg.voice_id)\n", (int)instrument->msg.voice_id);
        syslog(LOG_DEBUG, "MSG: %d (msg.type)\n", (int)instrument->msg.type);

#ifndef NOPYTHON
        // Relay a copy of all messages to python
        if(instrument->python_is_enabled) {
            if(send_message(instrument->python_message_relay_name, instrument->msg) < 0) {
                //PyErr_Print();
                syslog(LOG_ERR, "CPython error during renderer loop\n");
        // Relay a copy of all messages to python (or in the future maybe other things?)
        // except render complete messages which always get handled here...
        if(instrument->msg.type == LPMSG_RENDER_COMPLETE) {
            if(send_message(instrument->external_relay_name, instrument->msg) < 0) {
                syslog(LOG_ERR, "Could not relay message\n");
            }
        }
#endif

        // Handle shutdown early
        if(instrument->msg.type == LPMSG_SHUTDOWN) {
            syslog(LOG_DEBUG, "MSG: shutdown\n");
            instrument->is_running = 0;

        switch(instrument->msg.type) {
            case LPMSG_SHUTDOWN:
                syslog(LOG_DEBUG, "MSG: shutdown\n");
                instrument->is_running = 0;
                break;
            // send the shutdown message to the seq thread too
            if(relay_message_to_seq(instrument) < 0) {
                syslog(LOG_ERR, "%s renderer: Could not read relay message to seq. Error: (%d) %s\n", instrument->name, errno, strerror(errno));
            }
            break;
        }

            case LPMSG_RENDER_COMPLETE:
                syslog(LOG_DEBUG, "MSG: render complete\n");
                syslog(LOG_DEBUG, "%s\n", instrument->msg.msg);
        // Scheduled messages get sent to the sequencer for handling
        if(instrument->msg.scheduled > 0) {
            if(relay_message_to_seq(instrument) < 0) {
                syslog(LOG_ERR, "%s renderer: Could not read relay message to seq. Error: (%d) %s\n", instrument->name, errno, strerror(errno));
            }
            continue;
        }

                // Maybe just deserialize the buffer here for now? Everything blocks the main thread anyway...
        // Now the fun stuff
        switch(instrument->msg.type) {
            case LPMSG_RENDER_COMPLETE:
                /* FIXME do this in another thread */
                // Renders from the internal callback AND/OR external renderers (AKA python)
                if((buf = deserialize_buffer(instrument->msg.msg, &bufmsg)) == NULL) {
                    syslog(LOG_ERR, "DAC could not deserialize buffer. Error: (%d) %s\n", errno, strerror(errno));
                    continue;


@@ 2618,8 2652,10 @@ void * instrument_message_thread(void * arg) {
            case LPMSG_PLAY:
                syslog(LOG_DEBUG, "MSG: play\n");
                // Schedule a C callback render if there's a callback defined
                // python renders will also be triggered at this point if we're 
                // inside a python instrument because of the message relay
                if(instrument->renderer != NULL) {
                    /* Do the render: FIXME do this in another thread */
                    /* FIXME do this in another thread */
                    buf = instrument->renderer(instrument);

                    if(buf == NULL) {


@@ 2649,38 2685,19 @@ void * instrument_message_thread(void * arg) {
                break;

            case LPMSG_LOAD:
                // it would be interesting to explore live reloading of C modules
                // in the tradition of CLIVE, but at the moment only python handles these
                syslog(LOG_DEBUG, "MSG: load\n");
                break;

            case LPMSG_TRIGGER:
                // Python only handles these for now, but maybe it would be nice to have optional 
                // C callbacks here and maybe support raspberry pi GPIO pin toggling / triggers?
                syslog(LOG_DEBUG, "MSG: trigger\n");
                break;

            case LPMSG_SCHEDULE:
                syslog(LOG_DEBUG, "MSG: schedule\n");

                d = &instrument->pqnodes[pqnode_index];
                memcpy(&d->msg, &instrument->msg, sizeof(lpmsg_t));
                pqnode_index += 1;
                while(pqnode_index >= NUM_NODES) pqnode_index -= NUM_NODES;

                if(lpscheduler_get_now_seconds(&now) < 0) {
                    syslog(LOG_CRIT, "Error getting now in seq relay\n");
                    exit(1);
                }

                /* Hold on to the message as long as possible while still 
                 * trying to leave some time for processing before the target deadline */
                seq_delay = instrument->msg.scheduled - (instrument->msg.max_processing_time * 2);
                d->timestamp = instrument->msg.initiated + seq_delay;

                if(pqueue_insert(instrument->msgpq, (void *)d) < 0) {
                    syslog(LOG_ERR, "Error while inserting message into pq during msgq loop: %s\n", strerror(errno));
                    continue;
                }
                break;

            default:
                // Garbage typed messages will shut 'er down
                break;
        }
    }


@@ 2708,12 2725,6 @@ int astrid_instrument_seq_start(lpinstrument_t * instrument) {
        return -1;
    }

    /* Start message feed thread */
    if(pthread_create(&instrument->message_feed_thread, NULL, instrument_message_thread, (void*)instrument) != 0) {
        syslog(LOG_ERR, "Could not initialize instrument message thread. Error: %s\n", strerror(errno));
        return -1;
    }

    /* Start message pq thread */
    if(pthread_create(&instrument->message_scheduler_pq_thread, NULL, instrument_seq_pq, (void*)instrument) != 0) {
        syslog(LOG_ERR, "Could not initialize message scheduler pq thread. Error: %s\n", strerror(errno));


@@ 2785,9 2796,9 @@ lpinstrument_t * astrid_instrument_start(
     **/
    instrument->async_mixer = scheduler_create(1, instrument->channels, instrument->samplerate);

    printf("Python is enabled\n");
    instrument->python_is_enabled = 1;
    snprintf(instrument->python_message_relay_name, sizeof(instrument->python_message_relay_name), "/%s-python-relay", instrument->name);
    // Set the message q names
    snprintf(instrument->qname, NAME_MAX, "/%s-msgq", instrument->name);
    snprintf(instrument->external_relay_name, NAME_MAX, "/%s-extrelay-msgq", instrument->name);

    /* Open the LMDB session */
    astrid_instrument_session_open(instrument);


@@ 2869,19 2880,35 @@ lpinstrument_t * astrid_instrument_start(
    instrument->is_running = 1;
    syslog(LOG_INFO, "%s is running...\n", name);


    // Ready for some messages now! Open the message queue and start up the seq threads...
    if((instrument->playqd = astrid_playq_open(instrument->name)) == (mqd_t) -1) {
        syslog(LOG_CRIT, "Could not open playq for instrument %s. Error: %s\n", instrument->name, strerror(errno));
    if((instrument->msgq = astrid_msgq_open(instrument->qname)) == (mqd_t) -1) {
        syslog(LOG_CRIT, "Could not open msgq for instrument %s. Error: %s\n", instrument->name, strerror(errno));
        return NULL;
    }
    syslog(LOG_DEBUG, "Opened message queue for %s with fd %d\n", instrument->name, instrument->msgq);
    if((instrument->exmsgq = astrid_msgq_open(instrument->external_relay_name)) == (mqd_t) -1) {
        syslog(LOG_CRIT, "Could not open external message relay for instrument %s. Error: %s\n", instrument->name, strerror(errno));
        return NULL;
    }
    memcpy(instrument->msg.instrument_name, instrument->name, strlen(instrument->name));
    memcpy(instrument->cmd.instrument_name, instrument->name, strlen(instrument->name));
    syslog(LOG_DEBUG, "Opened play queue for %s with fd %d\n", instrument->name, instrument->playqd);
    syslog(LOG_DEBUG, "Opened message relay queue for %s with fd %d\n", instrument->name, instrument->exmsgq);

    // Write the instrument name into msg structs
    snprintf(instrument->msg.instrument_name, strlen(instrument->name)+1, instrument->name);
    snprintf(instrument->cmd.instrument_name, strlen(instrument->name)+1, instrument->name);

    // Start the message sequencer
    if(astrid_instrument_seq_start(instrument) < 0) {
        syslog(LOG_CRIT, "Could not start message sequence threads for instrument %s. Error: %s\n", instrument->name, strerror(errno));
        return NULL;
    }

    /* Start message feed thread */
    if(pthread_create(&instrument->message_feed_thread, NULL, instrument_message_thread, (void*)instrument) != 0) {
        syslog(LOG_ERR, "Could not initialize instrument message thread. Error: %s\n", strerror(errno));
        return NULL;
    }

    /* setup linenoise repl */
    linenoiseHistoryLoad("history.txt"); // FIXME this goes in the instrument config dir / or share?



@@ 2907,8 2934,8 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {
    }

    syslog(LOG_DEBUG, "Stopping message seq threads...\n");
    syslog(LOG_DEBUG, "Joining with message thread...\n");

    syslog(LOG_DEBUG, "Joining with message thread...\n");
    if((ret = pthread_join(instrument->message_feed_thread, NULL)) != 0) {
        if(ret == EINVAL) syslog(LOG_ERR, "EINVAL\n");
        if(ret == EDEADLK) syslog(LOG_ERR, "DEADLOCK\n");


@@ 2925,7 2952,7 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {
    }

    syslog(LOG_DEBUG, "Closing instrument message queue...\n");
    if(instrument->playqd != (mqd_t) -1) astrid_playq_close(instrument->playqd);
    if(instrument->msgq != (mqd_t) -1) astrid_msgq_close(instrument->msgq);

    syslog(LOG_DEBUG, "Stopping JACK...\n");
    for(c=0; c < instrument->channels; c++) {


@@ 3125,6 3152,45 @@ int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * buf
    return 0;
}

int astrid_instrument_console_readline(char * instrument_name) {
    char * line;
    lpmsg_t cmd;

    snprintf(cmd.instrument_name, strlen(instrument_name)+1, instrument_name);

    line = linenoise("^_- ");
    if(line != NULL) {
        printf("Got a cmd!\n");

        if(parse_message_from_cmdline(line, &cmd) < 0) {
            syslog(LOG_ERR, "Could not parse message from cmdline %s\n", line);
            return -1;
        }

        free(line);

        if(cmd.type == LPMSG_SERIAL) {
            if(send_serial_message(cmd) < 0) {
                syslog(LOG_ERR, "Could not send serial message...\n");
                return -1;
            }
        } else {
            printf("Sending the command on the %s q\n", cmd.instrument_name);
            if(send_play_message(cmd) < 0) {
                syslog(LOG_ERR, "Could not send play message...\n");
                return -1;
            }
        }

        if(cmd.type == LPMSG_SHUTDOWN) {
            return 1;
        }
    }


    return 0;
}

int astrid_instrument_tick(lpinstrument_t * instrument) {
    char * line;


M astrid/src/astrid.h => astrid/src/astrid.h +15 -8
@@ 161,34 161,39 @@ typedef struct lpinstrument_t {
    int has_been_initialized;
    lpfloat_t samplerate;

    // LMDB session refs
    MDB_cursor * dbcur;
    MDB_env * dbenv;
    MDB_dbi dbi;
    MDB_txn * dbtxn_read;
    MDB_txn * dbtxn_write;

    mqd_t playqd;
    // the XDG config dir where LMDB sessions live
    char datapath[PATH_MAX]; 

    // The instrument message q(s)
    char qname[NAME_MAX]; 
    char external_relay_name[NAME_MAX]; // just python, really 
    mqd_t msgq;
    mqd_t exmsgq;
    lpmsg_t msg;
    lpmsg_t cmd;

    // Message scheduling pq nodes
    pqueue_t * msgpq;
    lpmsgpq_node_t * pqnodes;

    // Thread refs
    pthread_t message_feed_thread;
    pthread_t message_scheduler_pq_thread;
    pthread_t buffer_feed_thread;
    lpscheduler_t * async_mixer;
    lpbuffer_t * lastbuf;

    // Jack refs
    jack_port_t ** inports;
    jack_port_t ** outports;
    jack_client_t * jack_client;

    char datapath[PATH_MAX];

    char python_message_relay_name[NAME_MAX];
    int python_is_enabled;

    // Optional local context struct for callbacks
    void * context;



@@ 271,7 276,7 @@ int astrid_playq_close(mqd_t mqd);

mqd_t astrid_msgq_open(char * qname);
int astrid_msgq_close(mqd_t mqd);
lpmsg_t astrid_msgq_read(mqd_t mqd);
int astrid_msgq_read(mqd_t mqd, lpmsg_t * msg);


/* TODO add POSIX message queues for these too */


@@ 331,6 336,8 @@ int astrid_instrument_tick(lpinstrument_t * instrument);
int astrid_instrument_session_open(lpinstrument_t * instrument);
int astrid_instrument_session_close(lpinstrument_t * instrument);
int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * bufstr, size_t size);
int astrid_instrument_console_readline(char * instrument_name);
int relay_message_to_seq(lpinstrument_t * instrument);

int lpencode_with_prefix(char * prefix, size_t val, char * encoded);
size_t lpdecode_with_prefix(char * encoded);

M libpippi/src/pippiconstants.h => libpippi/src/pippiconstants.h +71 -1
@@ 63,4 63,74 @@
#define LPMAXMSG (PIPE_BUF - (sizeof(double) * 4) - (sizeof(size_t) * 3) - sizeof(uint16_t) - LPMAXNAME)



enum Wavetables {
    WT_SINE,
    WT_COS,
    WT_SQUARE, 
    WT_TRI, 
    WT_TRI2, 
    WT_SAW,
    WT_RSAW,
    WT_RND,
    WT_USER,
    NUM_WAVETABLES
};

enum Windows {
    WIN_SINE,
    WIN_SINEIN,
    WIN_SINEOUT,
    WIN_COS,
    WIN_TRI, 
    WIN_PHASOR, 
    WIN_HANN, 
    WIN_HANNIN, 
    WIN_HANNOUT, 
    WIN_RND,
    WIN_SAW,
    WIN_RSAW,
    WIN_USER,
    NUM_WINDOWS
};

enum PanMethods {
    PANMETHOD_CONSTANT,
    PANMETHOD_LINEAR,
    PANMETHOD_SINE,
    PANMETHOD_GOGINS,
    NUM_PANMETHODS
};

enum LPMessageTypes {
    LPMSG_EMPTY,
    LPMSG_PLAY,
    LPMSG_TRIGGER,
    LPMSG_UPDATE,
    LPMSG_SERIAL,
    LPMSG_SCHEDULE,
    LPMSG_LOAD,
    LPMSG_RENDER_COMPLETE,
    LPMSG_SHUTDOWN,
    LPMSG_SET_COUNTER,
    NUM_LPMESSAGETYPES
};

// These serial message wrappers are experimental
enum LPSerialParamTypes {
    LPSERIAL_PARAM_BOOL,    /*  0 or 1 */
    LPSERIAL_PARAM_CTL,     /*  0 to 1 */
    LPSERIAL_PARAM_SIG,     /* -1 to 1 */
    LPSERIAL_PARAM_CHAR,    /* unsigned char */
    LPSERIAL_PARAM_INT,     /* signed int */
    LPSERIAL_PARAM_SIZE,    /* ssize_t */
    LPSERIAL_PARAM_UINT,    /* unsigned int */
    LPSERIAL_PARAM_USIZE,   /* size_t */
    LPSERIAL_PARAM_DOUBLE,  /* double */
    LPSERIAL_PARAM_FLOAT,   /* float */

    /* TODO add support for these */
    LPSERIAL_PARAM_MIDI,    /* midi bytes */
    LPSERIAL_PARAM_PCM,     /* PCM audio */
    LPSERIAL_PARAM_SHUTDOWN,
    NUM_LPSERIAL_PARAMS
};

M libpippi/src/pippicore.h => libpippi/src/pippicore.h +3 -0
@@ 236,4 236,7 @@ lpbuffer_t * lpbuffer_create_stack(lpbuffer_t * (*table_creator)(int name, size_

void pan_stereo_constant(lpfloat_t pos, lpfloat_t left_in, lpfloat_t right_in, lpfloat_t * left_out, lpfloat_t * right_out);

/* handle for built-in window TODO think about how to extend this in a simple way for other built-in compile time table defs */
extern const lpfloat_t LPHANN_WINDOW[];

#endif

M libpippi/src/pippitypes.h => libpippi/src/pippitypes.h +1 -74
@@ 21,8 21,6 @@ typedef struct lpbuffer_t {
    int is_looping;
} lpbuffer_t;

extern const lpfloat_t LPHANN_WINDOW[];

// Used for messaging between astrid instruments,
// but included in pippicore for embedded use and 
// external messaging support.


@@ 87,78 85,6 @@ typedef struct lpmsg_t {
    char instrument_name[LPMAXNAME];
} lpmsg_t;

enum Wavetables {
    WT_SINE,
    WT_COS,
    WT_SQUARE, 
    WT_TRI, 
    WT_TRI2, 
    WT_SAW,
    WT_RSAW,
    WT_RND,
    WT_USER,
    NUM_WAVETABLES
};

enum Windows {
    WIN_SINE,
    WIN_SINEIN,
    WIN_SINEOUT,
    WIN_COS,
    WIN_TRI, 
    WIN_PHASOR, 
    WIN_HANN, 
    WIN_HANNIN, 
    WIN_HANNOUT, 
    WIN_RND,
    WIN_SAW,
    WIN_RSAW,
    WIN_USER,
    NUM_WINDOWS
};

enum PanMethods {
    PANMETHOD_CONSTANT,
    PANMETHOD_LINEAR,
    PANMETHOD_SINE,
    PANMETHOD_GOGINS,
    NUM_PANMETHODS
};

enum LPMessageTypes {
    LPMSG_EMPTY,
    LPMSG_PLAY,
    LPMSG_TRIGGER,
    LPMSG_UPDATE,
    LPMSG_SERIAL,
    LPMSG_SCHEDULE,
    LPMSG_LOAD,
    LPMSG_RENDER_COMPLETE,
    LPMSG_SHUTDOWN,
    LPMSG_SET_COUNTER,
    NUM_LPMESSAGETYPES
};

// These serial message wrappers are experimental
enum LPSerialParamTypes {
    LPSERIAL_PARAM_BOOL,    /*  0 or 1 */
    LPSERIAL_PARAM_CTL,     /*  0 to 1 */
    LPSERIAL_PARAM_SIG,     /* -1 to 1 */
    LPSERIAL_PARAM_CHAR,    /* unsigned char */
    LPSERIAL_PARAM_INT,     /* signed int */
    LPSERIAL_PARAM_SIZE,    /* ssize_t */
    LPSERIAL_PARAM_UINT,    /* unsigned int */
    LPSERIAL_PARAM_USIZE,   /* size_t */
    LPSERIAL_PARAM_DOUBLE,  /* double */
    LPSERIAL_PARAM_FLOAT,   /* float */

    /* TODO add support for these */
    LPSERIAL_PARAM_MIDI,    /* midi bytes */
    LPSERIAL_PARAM_PCM,     /* PCM audio */
    LPSERIAL_PARAM_SHUTDOWN,
    NUM_LPSERIAL_PARAMS
};

typedef struct lpserialevent_t {
    double onset;
    double now;


@@ 173,3 99,4 @@ typedef struct lparray_t {
    size_t length;
    lpfloat_t phase;
} lparray_t;


M pippi/renderer.pxd => pippi/renderer.pxd +26 -12
@@ 39,10 39,6 @@ cdef extern from "pippicore.h":

    extern const lpbuffer_factory_t LPBuffer

cdef extern from "scheduler.h":
    ctypedef struct lpscheduler_t:
        pass

cdef extern from "astrid.h":
    cdef const int LPMAXMSG
    cdef const int LPMAXNAME


@@ 55,14 51,20 @@ cdef extern from "astrid.h":
    cdef const char * LPADC_BUFFER_PATH
    cdef const int NAME_MAX

    ctypedef struct lpscheduler_t:
        pass

    cdef enum LPMessageTypes:
        LPMSG_EMPTY,
        LPMSG_PLAY,
        LPMSG_TRIGGER,
        LPMSG_STOP_INSTRUMENT,
        LPMSG_STOP_VOICE,
        LPMSG_UPDATE,
        LPMSG_SERIAL,
        LPMSG_SCHEDULE,
        LPMSG_LOAD,
        LPMSG_RENDER_COMPLETE,
        LPMSG_SHUTDOWN,
        LPMSG_SET_COUNTER,
        NUM_LPMESSAGETYPES

    ctypedef struct lpmsg_t:


@@ 98,12 100,19 @@ cdef extern from "astrid.h":

    ctypedef struct lpinstrument_t:
        const char * name
        int channels
        volatile int is_running
        volatile int is_waiting
        int has_been_initialized
        int playqd
        char python_message_relay_name[NAME_MAX]
        int python_is_enabled
        lpfloat_t samplerate

        char qname[NAME_MAX]
        char external_relay_name[NAME_MAX]
        int msgq
        int exmsgq
        lpmsg_t msg
        lpmsg_t cmd

        lpscheduler_t * async_mixer

    int lpadc_create()


@@ 123,7 132,7 @@ cdef extern from "astrid.h":
    int midi_triggerq_close(int qfd)

    int astrid_msgq_open(char * qname)
    lpmsg_t astrid_msgq_read(int mqd)
    int astrid_msgq_read(int mqd, lpmsg_t * msg)
    int astrid_msgq_close(int mqd)

    int lpmidi_setcc(int device_id, int cc, int value)


@@ 133,8 142,12 @@ cdef extern from "astrid.h":

    int lpserial_getctl(int device_id, int ctl, lpfloat_t * value)

    void scheduler_schedule_event(lpscheduler_t * s, lpbuffer_t * buf, size_t delay)
    int lpscheduler_get_now_seconds(double * now)

    lpbuffer_t * deserialize_buffer(char * str, lpmsg_t * msg)
    int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * bufstr, size_t size)

    lpinstrument_t * astrid_instrument_start(
        const char * name, 
        int channels, 


@@ 146,8 159,9 @@ cdef extern from "astrid.h":

    int astrid_instrument_stop(lpinstrument_t * instrument)
    void scheduler_cleanup_nursery(lpscheduler_t * s)


    int astrid_instrument_console_readline(char * instrument_name)
    int relay_message_to_seq(lpinstrument_t * instrument)
    int send_play_message(lpmsg_t msg)

cdef class MessageEvent:
    cdef lpmsg_t * msg

M pippi/renderer.pyx => pippi/renderer.pyx +87 -85
@@ 6,17 6,17 @@ from libc.stdlib cimport calloc, free
from libc.string cimport strcpy, memcpy
import logging
from logging.handlers import SysLogHandler
import warnings
import importlib
import importlib.util
from multiprocessing import Process, Event
import os
from pathlib import Path
import platform
import struct
import subprocess
import sys
import threading
import time
import warnings

from pippi import dsp, midi
from pippi.soundbuffer cimport SoundBuffer


@@ 629,40 629,9 @@ cdef int trigger_events(object instrument, lpmsg_t * msg):
    return 0


ASTRID_INSTRUMENT = None

cdef public int astrid_reload_instrument(char * path) except -1:
    cdef size_t last_edit 
    global ASTRID_INSTRUMENT

    if ASTRID_INSTRUMENT is None:
        _path = path.decode('utf-8')
        name = Path(_path).stem
        ASTRID_INSTRUMENT = _load_instrument(name, _path)
        return 0

    last_edit = os.path.getmtime(ASTRID_INSTRUMENT.path)
    if last_edit > ASTRID_INSTRUMENT.last_reload:
        ASTRID_INSTRUMENT.reload()
        ASTRID_INSTRUMENT.last_reload = last_edit

    return 0


cdef public int astrid_load_instrument(char * path) except -1:
    global ASTRID_INSTRUMENT
    _path = path.decode('utf-8')
    name = Path(_path).stem

    print('NAME, _PATH', name, _path)

    ASTRID_INSTRUMENT = _load_instrument(name, _path)
    return 0

cdef public int astrid_schedule_python_render(void * msgp) except -1:
    global ASTRID_INSTRUMENT
cdef int astrid_schedule_python_render(Instrument instrument, void * msgp) except -1:
    cdef lpmsg_t * msg = <lpmsg_t *>msgp
    cdef size_t last_edit = os.path.getmtime(ASTRID_INSTRUMENT.path)
    cdef size_t last_edit = os.path.getmtime(instrument.path)
    cdef int render_result
    cdef double start = 0
    cdef double end = 0


@@ 672,89 641,122 @@ cdef public int astrid_schedule_python_render(void * msgp) except -1:
        return 1

    # Reload instrument
    if last_edit > ASTRID_INSTRUMENT.last_reload:
        ASTRID_INSTRUMENT.reload()
        ASTRID_INSTRUMENT.last_reload = last_edit
    if last_edit > instrument.last_reload:
        instrument.reload()
        instrument.last_reload = last_edit

    render_result = render_event(ASTRID_INSTRUMENT, msg)
    render_result = render_event(instrument, msg)

    if lpscheduler_get_now_seconds(&end) < 0:
        logger.exception('Error getting now seconds')
        return 1

    ASTRID_INSTRUMENT.max_processing_time = max(ASTRID_INSTRUMENT.max_processing_time, end - start)
    #logger.info('%s render time: %f seconds' % (ASTRID_INSTRUMENT.name, end - start))
    instrument.max_processing_time = max(instrument.max_processing_time, end - start)
    #logger.info('%s render time: %f seconds' % (instrument.name, end - start))

    return render_result


cdef public int astrid_schedule_python_triggers(void * msgp) except -1:
    global ASTRID_INSTRUMENT
cdef int astrid_schedule_python_triggers(Instrument instrument, void * msgp) except -1:
    cdef lpmsg_t * msg = <lpmsg_t *>msgp
    cdef size_t last_edit = os.path.getmtime(ASTRID_INSTRUMENT.path)
    cdef size_t last_edit = os.path.getmtime(instrument.path)

    # Reload instrument
    if last_edit > ASTRID_INSTRUMENT.last_reload:
        ASTRID_INSTRUMENT.reload()
        ASTRID_INSTRUMENT.last_reload = last_edit
    if last_edit > instrument.last_reload:
        instrument.reload()
        instrument.last_reload = last_edit

    try:
        return trigger_events(ASTRID_INSTRUMENT, msg)
        return trigger_events(instrument, msg)
    except Exception as e:
        logger.exception('Error during scheduling of python triggers: %s' % e)
        return -1

def run_forever(str instrument_name, str script_path):
    global ASTRID_INSTRUMENT
    cdef lpinstrument_t * instrument
    cdef lpmsg_t msg
    path = os.path.dirname(sys.argv[0])
    print('PATH', script_path) 
def _wait_on_commands_forever(str instrument_name, stop_event):
    instrument_byte_string = instrument_name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string

    # Start the stream and setup the instrument
    
    instrument = astrid_instrument_start('fake', 2, NULL, NULL, NULL, NULL)
    while True:
        if stop_event.is_set():
            break

    if instrument == NULL:
        logger.error('Error trying to start instrument. Shutting down...')
        return
        ret = astrid_instrument_console_readline(_instrument_ascii_name)

    if astrid_load_instrument('fake.py') < 0:
        raise Exception(f'Could not load instrument at path {path}')
        if ret < 0:
            logger.info('Could not read console line')
            time.sleep(0.5)
            continue

        if ret > 0:
            logger.info('Console has signaled stop, shutting down command loop...')
            break

    logger.info(f'loaded instrument {ASTRID_INSTRUMENT=}')
    name = ASTRID_INSTRUMENT.name.encode('ascii')
    logger.info(f'{name=}')
    cdef int q = astrid_msgq_open('/astridq-fake')
    logger.info(f'opened msgq {q}')
def _run_forever(str script_path, str instrument_name, int channels, stop_event):
    cdef Instrument instrument = None
    cdef lpinstrument_t * i = NULL
    cdef lpmsg_t msg, bufmsg
    cdef lpbuffer_t * buf = NULL
    instrument_byte_string = instrument_name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string

    logger.info(f'running forever... {script_path=} {instrument_name=}')

    # Load the script as a module into the ASTRID_INSTRUMENT global
    logger.info(f'loading python instrument... {script_path=} {instrument_name=}')
    instrument = _load_instrument(instrument_name, script_path)
    logger.info(f'loaded instrument {instrument=}')

    # Start the stream and setup the instrument
    logger.info(f'starting instrument... {script_path=} {instrument_name=}')
    i = astrid_instrument_start(_instrument_ascii_name, channels, NULL, NULL, NULL, NULL)
    if i == NULL:
        logger.error('Error trying to start instrument. Shutting down...')
        return

    while True:
        logger.info('reading messages...')
        msg = astrid_msgq_read(q)

        print('Got a message!')
        print('msg.type', msg.type)
        print('msg.msg', msg.msg)
        i.msg.scheduled = 0
        if astrid_msgq_read(i.exmsgq, &i.msg) < 0:
            print('There was a problem reading from the msg q. Maybe try turning it off and on again?')
            continue

        if msg.type == LPMSG_SHUTDOWN:
        if i.msg.type == LPMSG_SHUTDOWN:
            logger.info('Message loop got shutdown, breaking out!')
            stop_event.set()
            break

        elif msg.type == LPMSG_PLAY:
            if astrid_schedule_python_render(&msg) < 0:
        elif i.msg.type == LPMSG_PLAY:
            if astrid_schedule_python_render(instrument, &i.msg) < 0:
                logger.error('Error trying to schedule python render...')

        elif msg.type == LPMSG_TRIGGER:
            if astrid_schedule_python_triggers(&msg) < 0:
        elif i.msg.type == LPMSG_TRIGGER:
            if astrid_schedule_python_triggers(instrument, &i.msg) < 0:
                logger.error('Error trying to schedule python triggers...')

        elif msg.type == LPMSG_LOAD:
            if astrid_reload_instrument('fake.py') < 0:
                logger.error('Error trying to reload python module...')
        elif i.msg.type == LPMSG_LOAD:
            if instrument is None:
                instrument = _load_instrument(instrument_name, script_path)
            else:
                last_edit = os.path.getmtime(instrument.path)
                if last_edit > instrument.last_reload:
                    instrument.reload()
                    instrument.last_reload = last_edit


        scheduler_cleanup_nursery(instrument.async_mixer)
    logger.info('python instrument shutting down...')
    logger.info('done running forever!?')

    if astrid_msgq_close(q) < 0:
        logger.error('Could not close the python message queue')
def run_forever(str script_path, str instrument_name=None, channels=2):
    instrument_name = instrument_name if instrument_name is not None else Path(script_path).stem
    stop_event = Event()
    render_process = Process(target=_run_forever, args=(script_path, instrument_name, channels, stop_event))
    render_process.start()

    try:
        _wait_on_commands_forever(instrument_name, stop_event)
    except KeyboardInterrupt as e:
        print('Got keyboard interrupt')

    if astrid_instrument_stop(instrument) < 0:
        logger.error('Could not stop the background instrument threads')
    print('Waiting for the render process to complete')
    render_process.join()
    print('All done!')