~hecanjog/pippi

4a99a09281426702707bd16ba24b50c9f845a66a — Erik Schoster 6 months ago 556c0f8
Use flexible array members in libpippi buffers. More work on the new lpsampler interfaces and ADC in astrid. (WIP) Plus misc bugfixes.
M astrid/Makefile => astrid/Makefile +6 -5
@@ 52,11 52,12 @@ astrid-ipc:
	echo "Building astrid ipc tools...";

	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/getvoiceid.c $(LPLIBS) -o build/astrid-getvoiceid
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/createsharedbuffer.c $(LPLIBS) -o build/astrid-createsharedbuffer
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipccreatevalue.c $(LPLIBS) -o build/astrid-ipccreatevalue
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcgetvalue.c $(LPLIBS) -o build/astrid-ipcgetvalue
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcsetvalue.c $(LPLIBS) -o build/astrid-ipcsetvalue
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcdestroyvalue.c $(LPLIBS) -o build/astrid-ipcdestroyvalue
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/createsharedbuffer.c $(LPLIBS) -o build/astrid-createsharedbuffer
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipccreatevalue.c $(LPLIBS) -o build/astrid-ipccreatevalue
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcgetvalue.c $(LPLIBS) -o build/astrid-ipcgetvalue
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcsetvalue.c $(LPLIBS) -o build/astrid-ipcsetvalue
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcdestroyvalue.c $(LPLIBS) -o build/astrid-ipcdestroyvalue
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/sampler.c $(LPLIBS) -o build/astrid-sampler

astrid-q:
	echo "Building astrid queue reader...";

M astrid/orc/pulsar.c => astrid/orc/pulsar.c +10 -26
@@ 4,6 4,7 @@

#define SR 48000
#define CHANNELS 2
#define ADC_LENGTH 30

#define NUMOSCS (CHANNELS * 10)
#define WTSIZE 4096


@@ 34,7 35,6 @@ enum InstrumentParams {

typedef struct localctx_t {
    lppulsarosc_t * oscs[NUMOSCS];
    lpbuffer_t * ringbuf;
    lpbuffer_t * env;
    int last;



@@ 58,21 58,21 @@ void param_update_callback(void * arg) {
    astrid_instrument_set_param_float(instrument, PARAM_PW, LPRand.rand(0.05f, 1.f));
}

#if 0
lpbuffer_t * renderer_callback(void * arg) {
    lpbuffer_t * out;

    lpinstrument_t * instrument = (lpinstrument_t *)arg;
    localctx_t * ctx = (localctx_t *)instrument->context;

    out = LPBuffer.cut(ctx->ringbuf, LPRand.randint(0, SR*20), LPRand.randint(SR, SR*10));
    out = LPBuffer.cut(instrument->adcbuf, LPRand.randint(0, instrument->adcbuf->length/2), LPRand.randint(SR, instrument->adcbuf->length-2));
    LPFX.norm(out, LPRand.rand(0.26f, 0.5f));

    return out;
}
#endif

void audio_callback(int channels, size_t blocksize, float ** input, float ** output, void * arg) {
    size_t idx, i;
    int j, c;
void audio_callback(size_t blocksize, __attribute__((unused)) float ** input, float ** output, void * arg) {
    size_t i;
    int j;
    lpfloat_t freqs[NUMFREQS];
    lpfloat_t sample, left, right, amp, pw, saturation, pan;
    lpinstrument_t * instrument = (lpinstrument_t *)arg;


@@ 80,14 80,6 @@ void audio_callback(int channels, size_t blocksize, float ** input, float ** out

    if(!instrument->is_running) return;

    for(i=0; i < blocksize; i++) {
        idx = (ctx->ringbuf->pos + i) % ctx->ringbuf->length;
        for(c=0; c < channels; c++) {
            ctx->ringbuf->data[idx * channels + c] = input[c][i];
        }
    }
    ctx->ringbuf->pos += blocksize;

    amp = astrid_instrument_get_param_float(instrument, PARAM_AMP, 0.08f);
    pw = astrid_instrument_get_param_float(instrument, PARAM_PW, 1.f);
    astrid_instrument_get_param_float_list(instrument, PARAM_FREQS, NUMFREQS, freqs);


@@ 118,12 110,6 @@ void audio_callback(int channels, size_t blocksize, float ** input, float ** out
            while(ctx->curves[j]->phase >= 1.f) ctx->curves[j]->phase -= 1.f;
        }

        /*
        for(c=0; c < channels; c++) {
            output[c][i] += (float)sample * 0.5f;
        }
        */

        left = right = sample;
        pan_stereo_constant(pan, left, right, &left, &right);



@@ 142,9 128,8 @@ int main() {
        exit(1);
    }

    // create env and ringbuf
    // create env window for the footballs
    ctx->env = LPWindow.create(WIN_HANNOUT, 4096);
    ctx->ringbuf = LPBuffer.create(SR * 30, CHANNELS, SR);

    // setup oscs and curves
    for(int i=0; i < NUMOSCS; i++) {


@@ 163,8 148,8 @@ int main() {
    }

    // Set the callbacks for streaming, async renders and param updates
    if((instrument = astrid_instrument_start(NAME, CHANNELS, (void*)ctx, 
                    audio_callback, renderer_callback, param_update_callback)) == NULL) {
    if((instrument = astrid_instrument_start(NAME, CHANNELS, ADC_LENGTH, (void*)ctx, 
                    audio_callback, NULL, param_update_callback)) == NULL) {
        fprintf(stderr, "Could not start instrument: (%d) %s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }


@@ 192,7 177,6 @@ int main() {
        LPBuffer.destroy(ctx->curves[o]);
    }

    LPBuffer.destroy(ctx->ringbuf);
    LPBuffer.destroy(ctx->env);

    free(ctx);

M astrid/orc/simple.py => astrid/orc/simple.py +20 -2
@@ 1,12 1,30 @@
from pippi import dsp, oscs, renderer, fx
from pippi import dsp, oscs, renderer, fx, ugens

def stream(ctx):
    graph = ugens.Graph()
    graph.add_node('s0b', 'sine', freq=0.1)
    graph.add_node('s0a', 'sine', freq=0.1)
    graph.add_node('s0', 'sine', freq=100)
    graph.add_node('s1', 'sine')
    graph.add_node('s2', 'sine')

    graph.connect('s0b.output', 's0a.freq', 0.1, 200)
    graph.connect('s0a.output', 's0.freq', 100, 300)
    graph.connect('s0.output', 's1.freq', 0.1, 1)
    graph.connect('s0.output', 'main.output', mult=0.1)
    graph.connect('s1.output', 's2.freq', 60, 100)
    graph.connect('s2.output', 'main.output', mult=0.2)
    graph.connect('s2.freq', 's0b.freq', 0.15, 0.5, 60, 100)

    return graph

def trigger(ctx):
    return [ctx.t.play(0, 'simple'), ctx.t.trigger(dsp.rand(0.5, 1), 'simple')]
    #return None

def play(ctx):
    ctx.log('Rendering simple tone!')
    out = oscs.SineOsc(freq=dsp.rand(200, 1000), amp=dsp.rand(0.1, 0.5)).play(10).env('pluckout')
    out = out & ctx.adc(out.dur)

    if dsp.rand() > 0.5:
        out = fx.fold(out, dsp.rand(1, 2))

M astrid/src/astrid.c => astrid/src/astrid.c +308 -25
@@ 907,6 907,261 @@ int lpadc_read_block_of_samples(size_t offset, size_t size, lpfloat_t * out) {
    return 0;
}

int lpsampler_get_path(char * name, char * path) {
    snprintf(path, PATH_MAX, "/astrid-sampler-%s", name);
    return 0;
}

int lpsampler_get_data_path(char * name, char * path) {
    snprintf(path, PATH_MAX, "/astrid-sampler-%s-data", name);
    return 0;
}

int lpsampler_create(char * name, double length_in_seconds, int channels, int samplerate) {
    int semvalue, shmfd;
    sem_t * sem;
    lpbuffer_t * buf;
    size_t bufsize;
    char path[PATH_MAX] = {0};
    size_t length = (size_t)(length_in_seconds * samplerate);

    lpsampler_get_path(name, path);

    /* Determine the size of the shared memory segment */
    bufsize = sizeof(lpbuffer_t) + (length * channels * sizeof(lpfloat_t));
    printf("bufsize=%ld samplerate=%d length=%ld channels=%d\n", 
            bufsize, samplerate, length, channels);

    /* Create the POSIX semaphore and initialize it to 1 */
    if((sem = sem_open(path, O_CREAT, LPIPC_PERMS, 1)) == NULL) {
        syslog(LOG_ERR, "lpipc_buffer_create Could not create semaphore. (%s) %s\n", path, strerror(errno));
        return -1;
    }

    if(sem_getvalue(sem, &semvalue) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_create Could not get semaphore value. (%s) %s\n", path, strerror(errno));
        return -1;
    }

    /* initialize the shared memory segment for the lpbuffer_t struct */
    if((shmfd = shm_open(path, O_CREAT | O_RDWR, LPIPC_PERMS)) < 0) {
        syslog(LOG_ERR, "Could not create shared memory segment. (%s) %s\n", path, strerror(errno));
        return -1;
    }

    if(ftruncate(shmfd, bufsize) < 0) {
        syslog(LOG_ERR, "Could not truncate shared memory segment to size %ld. (%s) %s\n", sizeof(lpbuffer_t), path, strerror(errno));
        return -1;
    }

    if((buf = mmap(NULL, bufsize, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0)) == MAP_FAILED) {
        syslog(LOG_ERR, "Could not mmap shared memory segment to size %ld. (%s) %s\n", sizeof(lpbuffer_t), path, strerror(errno));
        return -1;
    }

    memset(buf, 0, bufsize);
    buf->channels = channels;
    buf->length = length;
    buf->samplerate = samplerate;
    buf->boundry = length-1;
    buf->range = length;

    return 0;
}

int lpsampler_aquire(char * name, lpbuffer_t ** buf) {
    struct stat statbuf;
    int fd;
    sem_t * sem;
    char path[PATH_MAX] = {0};
    lpsampler_get_path(name, path);

    /* Open the semaphore */
    if((sem = sem_open(path, 0)) == SEM_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_aquire failed to open semaphore %s. Error: %s\n", path, strerror(errno));
        return -1;
    }

    /* Aquire a lock on the semaphore */
    if(sem_wait(sem) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire failed to decrementsem %s. Error: %s\n", path, strerror(errno));
        return -1;
    }

    /* Get the file descriptor for the shared memory segment */
    if((fd = shm_open(path, O_RDWR, LPIPC_PERMS)) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not open shared memory segment. (%s) %s\n", path, strerror(errno));
        return -1;
    }

    /* Get the size of the segment */
    if(fstat(fd, &statbuf) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not stat shm. Error: %s\n", strerror(errno));
        return -1;
    }

    /* Attach the shared memory to the pointer */
    if((*buf = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not mmap shared memory segment to size %ld. (%s) %s\n", statbuf.st_size, path, strerror(errno));
        return -1;
    }

    /* Clean up sempahore resources */
    if(sem_close(sem) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire sem_close Could not close semaphore\n");
        return -1;
    }

    //close(fd);

    return 0;
}

int lpsampler_release(char * name) {
    sem_t * sem;
    char path[PATH_MAX] = {0};
    lpsampler_get_path(name, path);

    /* Open the semaphore */
    if((sem = sem_open(path, 0)) == SEM_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_release failed to open semaphore %s. Error: %s\n", path, strerror(errno));
        return -1;
    }

    /* Release the lock on the semaphore */
    if(sem_post(sem) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_release failed to unlock %s. Error: %s\n", path, strerror(errno));
        return -1;
    }

    /* Clean up sempahore resources */
    if(sem_close(sem) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_release sem_close Could not close semaphore\n");
        return -1;
    }

    return 0;
}


int lpsampler_destroy(char * name) {
    char path[PATH_MAX] = {0};
    lpsampler_get_path(name, path);

    /* Unlink the shared memory buffer */
    if(shm_unlink(path) < 0) {
        syslog(LOG_ERR, "lpsampler_destroy shm_unlink. Error: %s\n", strerror(errno));
        return -1;

    }

    /* Unlink the sempahore */
    if(sem_unlink(path) < 0) {
        syslog(LOG_ERR, "lpsampler_destroy sem_unlink Could not destroy semaphore\n");
        return -1;
    }

    return 0;
}

int lpsampler_write_ringbuffer_block(char * name, float ** block, int channels, size_t blocksize_in_samples) {
    size_t write_pos, insert_pos, i, boundry;
    int c;
    lpbuffer_t * buf;
    float sample = 0;
    char path[PATH_MAX] = {0};

    boundry = blocksize_in_samples * channels;
    lpsampler_get_path(name, path);

    /* Aquire a lock on the buffer */
    if(lpsampler_aquire(name, &buf) < 0) {
        syslog(LOG_ERR, "lpsampler_write_ringbuffer_block: Could not aquire ADC buffer shm for update\n");
        return -1;
    }

    printf("pos: %ld\n", buf->pos);
    write_pos = buf->pos;

    /* Copy the block of samples */
    for(i=0; i < blocksize_in_samples; i++) {
        for(c=0; c < channels; c++) {
            insert_pos = ((write_pos+i) * channels + c) % boundry;
            sample = *block[c]++;
            buf->data[insert_pos] = sample;
            printf("sample: %f\n", buf->data[insert_pos]);
        }
    }

    /* Increment the write position */
    write_pos += blocksize_in_samples;
    while(write_pos >= boundry) {
        write_pos -= boundry;
    }

    /* Store the new write position */
    buf->pos = write_pos;
    printf("pos: %ld\n", buf->pos);

    /* Release the lock on the ADC buffer shm */
    if(lpsampler_release(name) < 0) {
        syslog(LOG_ERR, "lpsampler_write_ringbuffer_block: Could not release buffer shm after update\n");
        return -1;
    }

    return 0;
}


int lpsampler_read_block_of_samples(char * name, size_t offset, size_t size, lpfloat_t * out) {
    size_t read_pos, start, end, readsize, lastreadsize, boundry;
    lpbuffer_t * buf;
    char path[PATH_MAX] = {0};

    lpsampler_get_path(name, path);
    boundry = buf->length * buf->channels;

    /* Aquire a lock on the buffer */
    if(lpsampler_aquire(name, &buf) < 0) {
        syslog(LOG_ERR, "Could not aquire ADC buffer shm for update\n");
        return -1;
    }

    /* Maximum read size == buffer length */
    if(size > boundry) size = boundry;

    /* Get the read position with the offset and read as far 
     * to the start of the buffer before wrapping as possible */
    if(buf->pos >= offset) {
        read_pos = (buf->pos - offset) % boundry;
    } else {
        read_pos = (buf->pos + (boundry - offset)) % boundry;
    }

    start = read_pos >= size ? read_pos - size : 0;
    end = read_pos;
    readsize = end - start;
    memcpy(out, buf->data + start, readsize * sizeof(lpfloat_t));

    /* If there are remaining samples to be read on the other end of 
     * the buffer, read those samples back from the end. */
if (readsize < size) {
        lastreadsize = readsize;
        start = boundry - (size - readsize);
        end = boundry;
        readsize = end - start;
        memcpy(out + lastreadsize, buf->data + start, readsize * sizeof(lpfloat_t));
    }

    /* Release the lock on the buffer shm */
    if(lpsampler_release(name) < 0) {
        syslog(LOG_ERR, "Could not release ADC buffer shm after update\n");
        return -1;
    }

    return 0;
}


int lpadc_read_sample(size_t offset, lpfloat_t * sample) {
    size_t write_pos;


@@ 1523,7 1778,7 @@ lpbuffer_t * deserialize_buffer(char * buffer_code, lpmsg_t * msg) {
    buf->channels = channels;
    buf->samplerate = samplerate;
    buf->is_looping = is_looping;
    buf->data = audio;
    memcpy(buf->data, audio, audiosize);
    buf->onset = onset;

    buf->phase = 0.f;


@@ 2369,12 2624,12 @@ void scheduler_cleanup_nursery(lpscheduler_t * s) {
    }
}

int send_serial_message(lpmsg_t msg) {
int send_serial_message(lpmsg_t msg, char * tty) {
    int fp;
    ssize_t bytes_written;

    if((fp = open(msg.instrument_name, O_WRONLY)) < 0) {
        syslog(LOG_CRIT, "Could not open %s for writing\n", msg.instrument_name);
    if((fp = open(tty, O_WRONLY)) < 0) {
        syslog(LOG_CRIT, "Could not open %s for writing\n", tty);
        return -1;
    }



@@ 2398,6 2653,9 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
    float * input_channels[instrument->channels];
    size_t i;
    int c;
    char path[PATH_MAX];

    snprintf(path, PATH_MAX, "%s-adc", instrument->name);

    if(!instrument->is_running) return 0;



@@ 2415,6 2673,11 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
        memset(output_channels[c], 0, nframes * sizeof(float));
    }

    /* write the block into the adc ringbuffer */
    if(lpsampler_write_ringbuffer_block(path, input_channels, instrument->channels, nframes) < 0) {
        syslog(LOG_ERR, "Error writing into adc ringbuf\n");
        return 0;
    }

    /* mix in async renders */
    if(instrument->async_mixer != NULL) {


@@ 2428,7 2691,6 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {

    if(instrument->stream != NULL) {
        instrument->stream(
            instrument->channels, 
            (size_t)nframes, 
            input_channels, output_channels, 
            (void *)instrument


@@ 2748,8 3010,9 @@ int astrid_instrument_seq_start(lpinstrument_t * instrument) {
lpinstrument_t * astrid_instrument_start(
    const char * name, 
    int channels, 
    double adc_length,
    void * ctx,
    void (*stream)(int channels, size_t blocksize, float ** input, float ** output, void * instrument),
    void (*stream)(size_t blocksize, float ** input, float ** output, void * instrument),
    lpbuffer_t * (*renderer)(void * instrument),
    void (*updates)(void * instrument)
) {


@@ 2796,17 3059,6 @@ lpinstrument_t * astrid_instrument_start(
        exit(1);
    }

    /* init scheduler
     * 
     * The scheduler is shared between the miniaudio callback 
     * and astrid buffer feed threads. The buffer feed thread 
     * may schedule buffers by adding them to the internal linked 
     * list in the scheduler. The miniaudio callback may read from 
     * the linked list, increment counts in playing buffers and 
     * flag buffers as having playback completed. 
     **/
    instrument->async_mixer = scheduler_create(1, instrument->channels, instrument->samplerate);

    // 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);


@@ 2833,9 3085,31 @@ lpinstrument_t * astrid_instrument_start(

    if((jack_status & JackServerStarted) == JackServerStarted) {
        syslog(LOG_INFO, "Jack server started!\n");
    }

    /* Get the samplerate from JACK */
    /* FIXME could use the JACK D-Bus API here to let instruments change the samplerate? */
    instrument->samplerate = (lpfloat_t)jack_get_sample_rate(instrument->jack_client);

    /* Create the internal ringbuffer for sampling from the adc */
    snprintf(instrument->adc_name, PATH_MAX, "%s-adc", instrument->name);
    if(lpsampler_create(instrument->adc_name, adc_length, instrument->channels, instrument->samplerate) < 0) {
        syslog(LOG_INFO, "Could not create instrument ADC buffer\n");
        goto astrid_instrument_shutdown_with_error;
    }

    /* init scheduler
     * 
     * The scheduler is shared between the miniaudio callback 
     * and astrid buffer feed threads. The buffer feed thread 
     * may schedule buffers by adding them to the internal linked 
     * list in the scheduler. The miniaudio callback may read from 
     * the linked list, increment counts in playing buffers and 
     * flag buffers as having playback completed. 
     **/
    instrument->async_mixer = scheduler_create(1, instrument->channels, instrument->samplerate);


    jack_set_process_callback(instrument->jack_client, astrid_instrument_jack_callback, (void *)instrument);
    for(c=0; c < channels; c++) {
        snprintf(outport_name, sizeof(outport_name), "out%d", c);


@@ 2864,25 3138,25 @@ lpinstrument_t * astrid_instrument_start(

    /* connect ports */
    if((ports = jack_get_ports(instrument->jack_client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput)) == NULL) {
		fprintf(stderr, "Cannot find any physical capture ports");
		exit(1);
		syslog(LOG_CRIT, "Cannot find any physical capture ports");
        goto astrid_instrument_shutdown_with_error;
	}

    for(c=0; c < instrument->channels; c++) {
        if(jack_connect(instrument->jack_client, ports[c], jack_port_name(instrument->inports[c]))) {
            fprintf(stderr, "cannot connect input ports\n");
            syslog(LOG_NOTICE, "Could not connect input port %d. (There probably aren't enough available inputs on this device.)\n", c);
        }
    }
	free(ports);
	
	if((ports = jack_get_ports(instrument->jack_client, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == NULL) {
		fprintf(stderr, "Cannot find any physical playback ports");
		exit(1);
		syslog(LOG_CRIT, "Cannot find any physical playback ports");
        goto astrid_instrument_shutdown_with_error;
	}

    for(c=0; c < instrument->channels; c++) {
        if(jack_connect(instrument->jack_client, jack_port_name(instrument->outports[c]), ports[c])) {
            fprintf(stderr, "cannot connect output ports\n");
            syslog(LOG_NOTICE, "Could not connect output port %d. (There probably aren't enough available outputs on this device.)\n", c);
        }
    }
	free(ports);


@@ 2944,6 3218,8 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {
        }
    }



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

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


@@ 2978,6 3254,13 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {

    if(instrument->async_mixer != NULL) scheduler_destroy(instrument->async_mixer);

    syslog(LOG_DEBUG, "Cleaning up adc ringbuf...\n");
    if(lpsampler_destroy(instrument->adc_name) < 0) {
        syslog(LOG_ERR, "Error while removing adc ringbuf, dang! Other cleanup is done tho.\n");
        closelog();
        return -1;
    }

    syslog(LOG_DEBUG, "All done, see ya later!\n");
    closelog();
    return 0;


@@ 3181,7 3464,7 @@ int astrid_instrument_console_readline(char * instrument_name) {
        free(line);

        if(cmd.type == LPMSG_SERIAL) {
            if(send_serial_message(cmd) < 0) {
            if(send_serial_message(cmd, cmd.instrument_name) < 0) {
                syslog(LOG_ERR, "Could not send serial message...\n");
                return -1;
            }


@@ 3217,7 3500,7 @@ int astrid_instrument_tick(lpinstrument_t * instrument) {

    if(line != NULL) {
        if(instrument->cmd.type == LPMSG_SERIAL) {
            if(send_serial_message(instrument->cmd) < 0) {
            if(send_serial_message(instrument->cmd, instrument->cmd.instrument_name) < 0) {
                syslog(LOG_ERR, "Could not send serial message...\n");
                return -1;
            }

M astrid/src/astrid.h => astrid/src/astrid.h +16 -4
@@ 106,6 106,7 @@ typedef struct lpmsgpq_node_t {
 * they schedule them (with the onset value, which 
 * is relative to *now*) by sending this struct over 
 * the MIDI trigger fifo. */
// FIXME these can probably just be normal lpmsg_t messages...?
typedef struct lpmidievent_t {
    double onset;
    double now;


@@ 171,6 172,9 @@ typedef struct lpinstrument_t {
    // the XDG config dir where LMDB sessions live
    char datapath[PATH_MAX]; 

    // The adc ringbuf name
    char adc_name[PATH_MAX];

    // The instrument message q(s)
    char qname[NAME_MAX]; 
    char external_relay_name[NAME_MAX]; // just python, really 


@@ 204,7 208,7 @@ typedef struct lpinstrument_t {
    lpbuffer_t * (*renderer)(void * instrument);

    // Stream callback
    void (*stream)(int channels, size_t blocksize, float ** input, float ** output, void * instrument);
    void (*stream)(size_t blocksize, float ** input, float ** output, void * instrument);

    // Shutdown signal handler (SIGTERM & SIGKILL)
    void (*shutdown)(int sig);


@@ 266,7 270,7 @@ int parse_message_from_cmdline(char * cmdline, lpmsg_t * msg);
ssize_t astrid_get_voice_id();

int send_message(char * qname, lpmsg_t msg);
int send_serial_message(lpmsg_t msg);
int send_serial_message(lpmsg_t msg, char * tty);
int send_play_message(lpmsg_t msg);
int get_play_message(char * instrument_name, lpmsg_t * msg);



@@ 306,16 310,24 @@ int lpadc_aquire(lpipc_buffer_t ** adcbuf);
int lpadc_increment_and_release(lpipc_buffer_t * adcbuf, size_t increment_in_samples);
int lpadc_write_block(const void * block, size_t blocksize);
int lpadc_read_sample(size_t offset, lpfloat_t * sample);
/*int lpadc_read_block_of_samples(size_t offset, size_t size, lpfloat_t (*out)[LPADCBUFSAMPLES]);*/
int lpadc_read_block_of_samples(size_t offset, size_t size, lpfloat_t * out);

int lpsampler_aquire(char * name, lpbuffer_t ** buf);
int lpsampler_release(char * name);
int lpsampler_read_block_of_samples(char * name, size_t offset, size_t size, lpfloat_t * out);
int lpsampler_write_ringbuffer_block(char * name, float ** block, int channels, size_t blocksize_in_samples);
int lpsampler_create(char * name, double length_in_seconds, int channels, int samplerate);
int lpsampler_destroy(char * name);

int lpipc_buffer_create(char * id_path, size_t length, int channels, int samplerate);
int lpipc_buffer_aquire(char * id_path, lpipc_buffer_t ** buf);
int lpipc_buffer_release(char * id_path);
int lpipc_buffer_tolpbuffer(lpipc_buffer_t * ipcbuf, lpbuffer_t ** buf);
int lpipc_buffer_destroy(char * id_path);

int lpipc_setid(char * path, int id); 
int lpipc_getid(char * path); 

int lpipc_createvalue(char * path, size_t size);
int lpipc_setvalue(char * path, void * value, size_t size);
int lpipc_getvalue(char * path, void ** value);


@@ 324,7 336,7 @@ int lpipc_destroyvalue(char * id_path);

void lptimeit_since(struct timespec * start);

lpinstrument_t * astrid_instrument_start(const char * name, int channels, void * ctx, void (*stream)(int channels, size_t blocksize, float ** input, float ** output, void * instrument), lpbuffer_t * (*renderer)(void * instrument), void (*updates)(void * instrument));
lpinstrument_t * astrid_instrument_start(const char * name, int channels, double adc_length, void * ctx, void (*stream)(size_t blocksize, float ** input, float ** output, void * instrument), lpbuffer_t * (*renderer)(void * instrument), void (*updates)(void * instrument));
int astrid_instrument_stop(lpinstrument_t * instrument);

void astrid_instrument_set_param_float(lpinstrument_t * instrument, int param_index, lpfloat_t value);

M astrid/src/msg.c => astrid/src/msg.c +1 -1
@@ 13,7 13,7 @@ int main(int argc, char * argv[]) {
    syslog(LOG_DEBUG, "             %d (msg.type)\n", (int)msg.type);

    if(msg.type == LPMSG_SERIAL) {
        if(send_serial_message(msg) < 0) {
        if(send_serial_message(msg, msg.instrument_name) < 0) {
            fprintf(stderr, "astrid-msg: Could not send serial message...\n");
            return 1;
        }

M banner.png => banner.png +0 -0
M libpippi/Makefile => libpippi/Makefile +2 -2
@@ 161,8 161,8 @@ convolution-examples:
	echo "Building convolution.c example...";
	gcc $(LPFLAGS) examples/convolution.c $(LPSOURCES) $(LPLIBS) -o build/convolution

	echo "Building convolution2.c example...";
	gcc $(LPFLAGS) examples/convolution2.c $(LPSOURCES) $(LPLIBS) -o build/convolution2
	#echo "Building convolution2.c example...";
	#gcc $(LPFLAGS) examples/convolution2.c $(LPSOURCES) $(LPLIBS) -o build/convolution2

soundfile-examples:
	mkdir -p build renders

M libpippi/examples/crossings.c => libpippi/examples/crossings.c +4 -0
@@ 31,6 31,10 @@ int main() {
                }
                /* increment the read index and add a fixed padding */
                wavesets[c]->boundry += wavesets[c]->pos + 222;
                if(wavesets[c]->boundry >= wavesets[c]->length) {
                    wavesets[c]->boundry -= wavesets[c]->length;
                }

                /* reset the position - new waveset */
                wavesets[c]->pos = 0;
            } else {

A libpippi/examples/raw.png => libpippi/examples/raw.png +0 -0
M libpippi/scripts/render_examples.sh => libpippi/scripts/render_examples.sh +1 -0
@@ 1,4 1,5 @@
#!/bin/bash
set -e

for b in build/*; do
    echo

M libpippi/src/oscs.pulsar.c => libpippi/src/oscs.pulsar.c +4 -0
@@ 96,6 96,8 @@ void create_pulsarosc_wavetable_stack(lppulsarosc_t * p, int numtables, va_list 
    LPMemoryPool.free(p->wavetable_onsets);
    LPMemoryPool.free(p->wavetable_lengths);

    if(numtables == 0) return;

    p->num_wavetables = numtables;
    p->wavetable_onsets = (size_t *)LPMemoryPool.alloc(numtables, sizeof(size_t));
    p->wavetable_lengths = (size_t *)LPMemoryPool.alloc(numtables, sizeof(size_t));


@@ 107,6 109,8 @@ void create_pulsarosc_window_stack(lppulsarosc_t * p, int numtables, va_list vl)
    LPMemoryPool.free(p->window_onsets);
    LPMemoryPool.free(p->window_lengths);

    if(numtables == 0) return;

    p->num_windows = numtables;
    p->window_onsets = (size_t *)LPMemoryPool.alloc(numtables, sizeof(size_t));
    p->window_lengths = (size_t *)LPMemoryPool.alloc(numtables, sizeof(size_t));

M libpippi/src/oscs.tape.c => libpippi/src/oscs.tape.c +2 -2
@@ 55,7 55,7 @@ void process_tapeosc(lptapeosc_t * osc) {
    channels = osc->buf->channels;
    boundry = osc->range + osc->start;

    printf("phase %f channels %i boundry %ld speed %f\n", osc->phase, channels, boundry, osc->speed);
    //printf("phase %f channels %i boundry %ld speed %f\n", osc->phase, channels, boundry, osc->speed);

    f = osc->phase - (int)osc->phase;
    idxa = (size_t)osc->phase;


@@ 69,7 69,7 @@ void process_tapeosc(lptapeosc_t * osc) {
    }

    osc->phase += osc->speed;
    printf("phase %f channels %i boundry %ld speed %f\n", osc->phase, channels, boundry, osc->speed);
    //printf("phase %f channels %i boundry %ld speed %f\n", osc->phase, channels, boundry, osc->speed);

    if(osc->phase >= boundry) {
        osc->phase = osc->start;

M libpippi/src/pippicore.c => libpippi/src/pippicore.c +4 -14
@@ 298,29 298,20 @@ void destroy_array(lparray_t * array) {
 * */
lpbuffer_t * create_buffer(size_t length, int channels, int samplerate) {
    lpbuffer_t * buf;
    buf = (lpbuffer_t*)LPMemoryPool.alloc(1, sizeof(lpbuffer_t));
    size_t bufsize = sizeof(lpbuffer_t) + (length * channels * sizeof(lpfloat_t));

    buf = (lpbuffer_t*)LPMemoryPool.alloc(1, bufsize);
    if(buf == NULL) {
        fprintf(stderr, "Could not alloc memory for buffer struct\n");
        return NULL;
    }

    buf->data = (lpfloat_t*)LPMemoryPool.alloc(length * channels, sizeof(lpfloat_t));
    if(buf->data == NULL) {
        fprintf(stderr, "Could not alloc memory for buffer data\n");
        if(buf != NULL) LPMemoryPool.free(buf);
        return NULL;
    }
    memset(buf->data, 0, length * channels * sizeof(lpfloat_t));

    memset(buf, 0, bufsize);
    buf->channels = channels;
    buf->length = length;
    buf->samplerate = samplerate;
    buf->phase = 0.f;
    buf->pos = 0;
    buf->boundry = length-1;
    buf->range = length;
    buf->onset = 0;
    buf->is_looping = 0;
    return buf;
}



@@ 1224,7 1215,6 @@ lpbuffer_t * resize_buffer(lpbuffer_t * buf, size_t length) {

void destroy_buffer(lpbuffer_t * buf) {
    if(buf != NULL) {
        LPMemoryPool.free(buf->data);
        LPMemoryPool.free(buf);
    }
}

M libpippi/src/pippitypes.h => libpippi/src/pippitypes.h +1 -10
@@ 7,7 7,6 @@ typedef double lpfloat_t;

/* Core datatypes */
typedef struct lpbuffer_t {
    lpfloat_t * data;
    size_t length;
    int samplerate;
    int channels;


@@ 19,6 18,7 @@ typedef struct lpbuffer_t {
    size_t pos;
    size_t onset;
    int is_looping;
    lpfloat_t data[];
} lpbuffer_t;

// Used for messaging between astrid instruments,


@@ 86,15 86,6 @@ typedef struct lpmsg_t {
    char instrument_name[LPMAXNAME];
} lpmsg_t;

typedef struct lpserialevent_t {
    double onset;
    double now;
    double length;
    lpmsg_t msg;
    int group;
    int device;
} lpserialevent_t;

typedef struct lparray_t {
    int * data;
    size_t length;

M pippi/buffers.pxd => pippi/buffers.pxd +4 -2
@@ 35,14 35,16 @@ cdef extern from "pippicore.h":
        NUM_PANMETHODS

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels

        lpfloat_t phase
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lpwavetable_factory_t:
        lpbuffer_t * (*create)(int name, size_t length)

M pippi/grains2.pxd => pippi/grains2.pxd +3 -1
@@ 18,7 18,6 @@ cdef extern from "pippicore.h":
    ctypedef double lpfloat_t

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels


@@ 26,6 25,9 @@ cdef extern from "pippicore.h":
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lpstack_t:
        lpbuffer_t ** stack

M pippi/mir.pxd => pippi/mir.pxd +3 -1
@@ 8,7 8,6 @@ cdef extern from "pippicore.h":
    ctypedef double lpfloat_t

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels


@@ 16,6 15,9 @@ cdef extern from "pippicore.h":
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

cdef extern from "mir.h":
    ctypedef struct lpyin_t:

M pippi/noise.pxd => pippi/noise.pxd +3 -1
@@ 13,7 13,6 @@ cdef extern from "pippicore.h":
    ctypedef double lpfloat_t

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels


@@ 21,6 20,9 @@ cdef extern from "pippicore.h":
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lpstack_t:
        lpbuffer_t ** stack

M pippi/pulsar2d.pxd => pippi/pulsar2d.pxd +3 -1
@@ 5,7 5,6 @@ from pippi.interpolation cimport interp_point_t
cdef extern from "pippicore.h":
    ctypedef double lpfloat_t
    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels


@@ 13,6 12,9 @@ cdef extern from "pippicore.h":
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lparray_t:
        int * data

M pippi/renderer.pxd => pippi/renderer.pxd +33 -37
@@ 7,14 7,16 @@ cdef extern from "pippicore.h":
    ctypedef double lpfloat_t

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels

        lpfloat_t phase
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lpbuffer_factory_t: 
        lpbuffer_t * (*create)(size_t, int, int)


@@ 73,10 75,10 @@ cdef extern from "astrid.h":
        NUM_LPMESSAGETYPES

    ctypedef struct lpmsg_t:
        double initiated;     
        double scheduled;     
        double completed;     
        double max_processing_time;     
        double initiated
        double scheduled 
        double completed
        double max_processing_time
        size_t onset_delay
        size_t voice_id
        size_t count


@@ 97,13 99,6 @@ cdef extern from "astrid.h":
        char bank_lsb
        char channel

    ctypedef struct lpserialevent_t:
        double onset
        double now
        lpmsg_t msg
        int group
        int device

    ctypedef struct lpinstrument_t:
        const char * name
        int channels


@@ 121,12 116,7 @@ cdef extern from "astrid.h":

        lpscheduler_t * async_mixer

    int lpadc_create()
    int lpadc_destroy()
    int lpadc_write_block(float * block, size_t blocksize_in_samples)
    int lpadc_read_sample(size_t pos, lpfloat_t * sample)
    #int lpadc_read_block_of_samples(size_t offset, size_t size, lpfloat_t (*out)[LPADCBUFSAMPLES])
    int lpadc_read_block_of_samples(size_t offset, size_t size, lpfloat_t * out)
    int lpsampler_read_block_of_samples(char * name, size_t offset, size_t size, lpfloat_t * out)

    int lpipc_getid(char * path)
    ssize_t astrid_get_voice_id()


@@ 157,8 147,9 @@ cdef extern from "astrid.h":
    lpinstrument_t * astrid_instrument_start(
        const char * name, 
        int channels, 
        double adc_length,
        void * ctx, 
        void (*stream)(int channels, size_t blocksize, float ** input, float ** output, void * instrument),
        void (*stream)(size_t blocksize, float ** input, float ** output, void * instrument),
        lpbuffer_t * (*renderer)(void * instrument),
        void (*updates)(void * instrument)
    )


@@ 168,6 159,7 @@ cdef extern from "astrid.h":
    int astrid_instrument_console_readline(char * instrument_name)
    int relay_message_to_seq(lpinstrument_t * instrument)
    int send_play_message(lpmsg_t msg)
    int send_serial_message(lpmsg_t msg, char * tty);

cdef class MessageEvent:
    cdef lpmsg_t * msg


@@ 177,10 169,6 @@ cdef class MidiEvent:
    cdef lpmidievent_t * event
    cpdef int schedule(MidiEvent self, double now)

cdef class SerialEvent:
    cdef lpserialevent_t * event
    cpdef int schedule(SerialEvent self, double now)

cdef class SessionParamBucket:
    cdef object _bus



@@ 201,6 189,21 @@ cdef class MidiEventListenerProxy:
cdef class SerialEventListenerProxy:
    cpdef lpfloat_t ctl(self, int ctl, int device_id=*)

cdef class Instrument:
    cdef public str name
    cdef public str path
    cdef public object renderer
    cdef public object graph
    cdef public dict cache
    cdef public lpmsg_t msg # a copy of the last message received
    cdef public size_t last_reload
    cdef public double max_processing_time
    cdef public int default_midi_device
    cdef lpinstrument_t * i
    cpdef EventContext get_event_context(Instrument self, bint with_graph=*)
    cpdef lpmsg_t get_message(Instrument self)
    cdef SoundBuffer read_from_adc(Instrument self, double length, double offset=*, int channels=*, int samplerate=*)

cdef class EventContext:
    cdef public dict cache
    cdef public ParamBucket p


@@ 210,23 213,16 @@ cdef class EventContext:
    cdef public SerialEventListenerProxy b
    cdef public str instrument_name
    cdef public object sounds
    cdef public object graph
    cdef public int count
    cdef public int tick
    cdef public int vid
    cdef public double now
    cdef public double max_processing_time
    cdef public Instrument instrument

cdef class Instrument:
    cdef public str name
    cdef public str path
    cdef public object renderer
    cdef public dict cache
    cdef public size_t last_reload
    cdef public double max_processing_time
    cdef public int default_midi_device

cdef tuple collect_players(object instrument)
cdef int render_event(object instrument, lpmsg_t * msg)
cdef set collect_trigger_planners(object instrument)
cdef int trigger_events(object instrument, lpmsg_t * msg)
cdef tuple collect_players(Instrument instrument)
cdef int render_event(Instrument instrument)
cdef set collect_trigger_planners(Instrument instrument)
cdef int trigger_events(Instrument instrument)


M pippi/renderer.pyx => pippi/renderer.pyx +172 -168
@@ 18,10 18,13 @@ import sys
import time
import warnings

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


class InstrumentError(Exception):
    pass

logger = logging.getLogger('astrid-cyrenderer')
if not logger.handlers:
    if platform.system() == 'Darwin':


@@ 35,7 38,7 @@ if not logger.handlers:

cdef lpfloat_t[LPADCBUFSAMPLES] adc_block

cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t * msg):
cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t msg):
    cdef bytearray strbuf
    cdef size_t audiosize, length, msgsize
    cdef int channels, samplerate


@@ 63,31 66,12 @@ cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t * msg):
        for c in range(channels):
            strbuf += struct.pack('d', buf.frames[i,c])

    msgview = <unsigned char[:msgsize]>(<unsigned char *>msg)
    msgview = <unsigned char[:msgsize]>(<unsigned char *>&msg)
    for i in range(msgsize):
        strbuf.append(msgview[i])

    return bytes(strbuf)

cdef SoundBuffer read_from_adc(double length, double offset=0, int channels=2, int samplerate=48000):
    cdef size_t i
    cdef int c

    cdef SoundBuffer snd = SoundBuffer(length=length, channels=channels, samplerate=samplerate)
    cdef size_t length_in_frames = len(snd)
    cdef size_t offset_in_frames = <size_t>(offset * samplerate)

    if lpadc_read_block_of_samples(offset_in_frames * channels, length_in_frames * channels, adc_block) < 0:
        logger.error('cyrenderer ADC read: failed to read %d frames at offset %d from ADC' % (length_in_frames, offset_in_frames))
        return snd

    for i in range(length_in_frames):
        for c in range(channels):
            snd.frames[i,c] = adc_block[i * channels + c]

    return snd


cdef class MessageEvent:
    def __cinit__(self,
            double onset,


@@ 96,7 80,6 @@ cdef class MessageEvent:
            str params,
            double max_processing_time
        ):

        cdef size_t onset_frames = 0

        params_byte_string = params.encode('utf-8')


@@ 121,52 104,14 @@ cdef class MessageEvent:

    cpdef int schedule(MessageEvent self, double now):
        self.msg.initiated = now
        #logger.debug('CYRENDERER: Sending message type %d to %s' % (self.msg.type, self.msg.instrument_name))
        #logger.debug('initiated=%f scheduled=%f voice_id=%d' % (self.msg.initiated, self.msg.scheduled, self.msg.voice_id))
        if self.msg.type == LPMSG_SERIAL:
            return send_serial_message(self.msg[0], self.msg.instrument_name)
        return send_play_message(self.msg[0])

    def __dealloc__(self):
        if self.msg is not NULL:
            free(self.msg)

cdef class SerialEvent:
    def __cinit__(self,
            double onset,
            lpmsg_t msg,
            int group=0, 
            int device=0
        ):

        self.event = <lpserialevent_t *>calloc(1, sizeof(lpserialevent_t))
        self.event.onset = onset
        self.event.now = 0
        self.event.msg = msg
        self.event.group = group
        self.event.device = device

    cpdef int schedule(SerialEvent self, double now):
        """
        self.event.now = now
        cdef int qfd = midi_triggerq_open()
        if qfd < 0:
            logger.exception('Error opening MIDI fifo q')
            return -1

        if midi_triggerq_schedule(qfd, self.event[0]) < 0:
            logger.exception('Error scheduling MidiEvent')
            return -1

        if midi_triggerq_close(qfd) < 0:
            logger.exception('Error closing MIDI fifo q')
            return -1
        """

        return 0 

    def __dealloc__(self):
        if self.event is not NULL:
            free(self.event)


cdef class MidiEvent:
    def __cinit__(self,


@@ 280,6 225,9 @@ cdef class EventTriggerFactory:
        params = self._parse_params(*args, **kwargs)
        return MessageEvent(onset, instrument_name, LPMSG_TRIGGER, params, 0)

    def serial(self, double onset, str tty, *args, **kwargs):
        params = self._parse_params(*args, **kwargs)
        return MessageEvent(onset, tty, LPMSG_SERIAL, params, 0)


cdef class SessionParamBucket:


@@ 334,9 282,10 @@ cdef class ParamBucket:

cdef class EventContext:
    def __cinit__(self, 
            str instrument_name=None, 
            Instrument instrument,
            str msg=None,
            dict cache=None,
            object graph=None,
            int voice_id=-1,
            int default_midi_device=1,
            double max_processing_time=1,


@@ 345,21 294,22 @@ cdef class EventContext:

        cdef double now = 0

        self.graph = graph
        self.cache = cache
        self.p = ParamBucket(str(msg))
        self.s = SessionParamBucket() 
        self.t = EventTriggerFactory()
        self.m = MidiEventListenerProxy(default_midi_device)
        self.instrument_name = instrument_name
        self.instrument = instrument
        self.vid = voice_id
        self.count = count
        if lpscheduler_get_now_seconds(&now) < 0:
            logger.exception('Error getting now seconds during %s event ctx init' % instrument_name)
            logger.exception('Error getting now seconds during %s event ctx init' % instrument.name)
            now = 0
        self.now = now

    def adc(self, length=1, offset=0, channels=2):
        return read_from_adc(length, offset=offset, channels=channels)
        return self.instrument.read_from_adc(length, offset=offset, channels=channels)

    def log(self, msg):
        logger.info('ctx.log[%s] %s' % (self.instrument_name, msg))


@@ 368,14 318,112 @@ cdef class EventContext:
        return self.p._params

cdef class Instrument:
    def __init__(self, str name, str path, object renderer):
    def __init__(self, str name, str path, int channels, double adc_length):
        instrument_byte_string = name.encode('UTF-8')
        cdef char * _instrument_ascii_name = instrument_byte_string

        self.name = name
        self.path = path
        self.renderer = renderer
        self.cache = {}
        self.last_reload = 0
        self.max_processing_time = 0

        self.i = astrid_instrument_start(_instrument_ascii_name, channels, adc_length, NULL, NULL, NULL, NULL)
        if self.i == NULL:
            raise InstrumentError('Could not initialize lpinstrument_t')

        self.load_renderer(name, path)

    def load_renderer(self, name, path):
        """ Loads a renderer module from the script 
            at self.path 

            Failure to load the module raises an 
            InstrumentNotFoundError
        """
        try:
            logger.debug('Loading instrument %s from %s' % (name, path))
            spec = importlib.util.spec_from_file_location(name, path)
            if spec is not None:
                renderer = importlib.util.module_from_spec(spec)
                try:
                    spec.loader.exec_module(renderer)
                except Exception as e:
                    logger.exception('Error loading instrument module: %s' % str(e))

                if not hasattr(renderer, '_'):
                    renderer._ = None

                self.renderer = renderer

                # fill the cache if there is something to fill:
                if hasattr(self.renderer, 'cache'):
                    self.cache = self.renderer.cache()

                if hasattr(self.renderer, 'MIDI_DEVICE'):
                    self.default_midi_device = self.renderer.MIDI_DEVICE
                else:
                    self.default_midi_device = 1

            else:
                logger.error('Could not load instrument - spec is None: %s %s' % (path, name))
                raise InstrumentError('Instrument renderer has a null spec')

        except TypeError as e:
            logger.exception('TypeError loading renderer module: %s' % str(e))
            raise InstrumentError('Problem loading the python renderer') from e

        logger.info('loaded instrument, setting metadata')
        self.last_reload = os.path.getmtime(path)
        #logger.info('registering midi triggers')
        #self.register_midi_triggers()

    cpdef lpmsg_t get_message(Instrument self):
        cdef lpmsg_t msg
        if astrid_msgq_read(self.i.exmsgq, &msg) < 0:
            raise InstrumentError('Could not get the instrument message')
        self.msg = msg # so many copies omg
        return msg

    cpdef EventContext get_event_context(Instrument self, bint with_graph=False):
        cdef bytes render_params = self.msg.msg
        msgstr = render_params.decode('ascii')
        graph = None
        if with_graph:
            graph = ugens.Graph()

        return EventContext.__new__(EventContext,
            self,
            msg=msgstr,
            cache=self.cache,
            graph=graph,
            voice_id=self.msg.voice_id,
            max_processing_time=self.max_processing_time,
            default_midi_device=self.default_midi_device,
            count=self.msg.count,
        )

    cdef SoundBuffer read_from_adc(Instrument self, double length, double offset=0, int channels=2, int samplerate=48000):
        cdef size_t i
        cdef int c
        name_byte_string = ("%s-adc" % self.name).encode('UTF-8')
        cdef char * _ascii_name = name_byte_string

        cdef SoundBuffer snd = SoundBuffer(length=length, channels=channels, samplerate=samplerate)
        cdef size_t length_in_frames = len(snd)
        cdef size_t offset_in_frames = <size_t>(offset * samplerate)

        # fixme add dcblock
        if lpsampler_read_block_of_samples(_ascii_name, offset_in_frames * channels, length_in_frames * channels, adc_block) < 0:
            logger.error('pippi.renderer ADC read: failed to read %d frames at offset %d from ADC' % (length_in_frames, offset_in_frames))
            return snd

        for i in range(length_in_frames):
            for c in range(channels):
                snd.frames[i,c] = adc_block[i * channels + c]

        return snd

    def reload(self):
        logger.debug('Reloading instrument %s from %s' % (self.name, self.path))
        spec = importlib.util.spec_from_file_location(self.name, self.path)


@@ 408,56 456,27 @@ cdef class Instrument:
                devices = [ self.renderer.MIDI ]


class InstrumentNotFoundError(Exception):
    def __init__(self, instrument_name, *args, **kwargs):
        self.message = 'No instrument named %s found' % instrument_name
cdef int stream_graph_update(object instrument):
    cdef EventContext ctx 

def _load_instrument(name, path):
    """ Loads a renderer module from the script 
        at self.path 
    if not hasattr(instrument.renderer, 'stream'):
        return 0

        Failure to load the module raises an 
        InstrumentNotFoundError
    """
    ctx = instrument.get_event_context()
    try:
        logger.debug('Loading instrument %s from %s' % (name, path))
        spec = importlib.util.spec_from_file_location(name, path)
        if spec is not None:
            renderer = importlib.util.module_from_spec(spec)
            try:
                spec.loader.exec_module(renderer)
            except Exception as e:
                logger.exception('Error loading instrument module: %s' % str(e))

            if not hasattr(renderer, '_'):
                renderer._ = None

            instrument = Instrument(name, path, renderer)

            # fill the cache if there is something to fill:
            if hasattr(instrument.renderer, 'cache'):
                instrument.cache = instrument.renderer.cache()
        # Update the graph
        instrument.graph = instrument.renderer.stream(ctx)
    except Exception as e:
        logger.exception('Error during %s stream graph update: %s' % (ctx.instrument.name, e))
        return -1

            if hasattr(instrument.renderer, 'MIDI_DEVICE'):
                instrument.default_midi_device = instrument.renderer.MIDI_DEVICE
            else:
                instrument.default_midi_device = 1
    return 0

        else:
            logger.error('Could not load instrument - spec is None: %s %s' % (path, name))
            raise InstrumentNotFoundError(name)
    except TypeError as e:
        logger.exception('TypeError loading instrument module: %s' % str(e))
        raise InstrumentNotFoundError(name) from e

    logger.info('loaded instrument, setting metadata')
    instrument.last_reload = os.path.getmtime(path)
    #logger.info('registering midi triggers')
    #instrument.register_midi_triggers()
    return instrument

cdef tuple collect_players(object instrument):
cdef tuple collect_players(Instrument instrument):
    loop = False
    # FIXME it's still nice to support this, but 
    # it should just schedule a message instead of 
    # storing a flag on the serialized buffer...
    if hasattr(instrument.renderer, 'LOOP'):
        loop = instrument.renderer.LOOP



@@ 482,25 501,17 @@ cdef tuple collect_players(object instrument):
    
    return players, loop

cdef int render_event(object instrument, lpmsg_t * msg):
cdef int render_event(Instrument instrument):
    cdef set players
    cdef bint loop
    cdef EventContext ctx 
    cdef str msgstr
    cdef bytes bufstr
    cdef bytes render_params = msg.msg
    cdef bytes render_params = instrument.msg.msg
    cdef int dacid = 0
    cdef EventContext ctx 
    instrument_byte_string = instrument.name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string

    msgstr = render_params.decode('ascii')
    ctx = EventContext.__new__(EventContext,
        instrument_name=instrument.name, 
        msg=msgstr,
        cache=instrument.cache,
        voice_id=msg.voice_id,
        default_midi_device=instrument.default_midi_device,
        max_processing_time=instrument.max_processing_time,
        count=msg.count,
    )
    ctx = instrument.get_event_context()

    logger.debug('rendering event %s w/params %s' % (str(instrument), render_params))



@@ 525,14 536,14 @@ cdef int render_event(object instrument, lpmsg_t * msg):
                    else:
                        dacid = 0

                    bufstr = serialize_buffer(snd, loop, msg)
                    astrid_instrument_publish_bufstr(msg.instrument_name, bufstr, len(bufstr))
                    bufstr = serialize_buffer(snd, loop, instrument.msg)
                    astrid_instrument_publish_bufstr(_instrument_ascii_name, bufstr, len(bufstr))

            except Exception as e:
                logger.exception('Error during %s generator render: %s' % (ctx.instrument_name, e))
                logger.exception('Error during %s generator render: %s' % (instrument.name, e))
                return 1
        except Exception as e:
            logger.exception('Error allocating generator for %s render: %s' % (ctx.instrument_name, e))
            logger.exception('Error allocating generator for %s render: %s' % (instrument.name, e))
            return 1

    if hasattr(instrument.renderer, 'done'):


@@ 540,7 551,7 @@ cdef int render_event(object instrument, lpmsg_t * msg):

    return 0

cdef set collect_trigger_planners(object instrument):
cdef set collect_trigger_planners(Instrument instrument):
    # find all trigger planner functions
    planners = set()



@@ 562,30 573,20 @@ cdef set collect_trigger_planners(object instrument):
    
    return planners

cdef int trigger_events(object instrument, lpmsg_t * msg):
cdef int trigger_events(Instrument instrument):
    """ Collect the trigger functions in the instrument module
        and compute the triggers to be scheduled.
    """
    cdef set planners
    cdef bint loop
    cdef EventContext ctx 
    cdef str msgstr
    cdef list eventlist
    cdef int qfd
    cdef double now = 0
    cdef bytes trigger_params = msg.msg
    cdef bytes trigger_params = instrument.msg.msg
    cdef list trigger_events = []

    msgstr = trigger_params.decode('ascii')
    ctx = EventContext.__new__(EventContext,
        instrument_name=instrument.name, 
        msg=msgstr,
        cache=instrument.cache,
        voice_id=msg.voice_id,
        max_processing_time=instrument.max_processing_time,
        default_midi_device=instrument.default_midi_device,
        count=msg.count,
    )
    ctx = instrument.get_event_context()

    #logger.debug('trigger generation event %s w/params %s' % (str(instrument), trigger_params))



@@ 604,7 605,7 @@ cdef int trigger_events(object instrument, lpmsg_t * msg):
                continue
            trigger_events += eventlist
        except Exception as e:
            logger.exception('Error during %s trigger generation: %s' % (ctx.instrument_name, e))
            logger.exception('Error during %s trigger generation: %s' % (ctx.instrument.name, e))
            return 1

    # Schedule the trigger events


@@ 628,8 629,7 @@ cdef int trigger_events(object instrument, lpmsg_t * msg):
    return 0


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


@@ 644,7 644,7 @@ cdef int astrid_schedule_python_render(Instrument instrument, void * msgp) excep
        instrument.reload()
        instrument.last_reload = last_edit

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

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


@@ 656,8 656,7 @@ cdef int astrid_schedule_python_render(Instrument instrument, void * msgp) excep
    return render_result


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

    # Reload instrument


@@ 666,7 665,7 @@ cdef int astrid_schedule_python_triggers(Instrument instrument, void * msgp) exc
        instrument.last_reload = last_edit

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


@@ 690,9 689,8 @@ def _wait_on_commands_forever(str instrument_name, stop_event):
            logger.info('Console has signaled stop, shutting down command loop...')
            break

def _run_forever(str script_path, str instrument_name, int channels, stop_event):
def _run_forever(str script_path, str instrument_name, int channels, double adc_length, stop_event):
    cdef Instrument instrument = None
    cdef lpinstrument_t * i = NULL
    cdef lpmsg_t msg
    instrument_byte_string = instrument_name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string


@@ 700,20 698,21 @@ def _run_forever(str script_path, str instrument_name, int channels, stop_event)
    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:
    try:
        logger.info(f'loading python instrument... {script_path=} {instrument_name=}')
        instrument = Instrument(instrument_name, script_path, channels, adc_length)
        logger.info(f'loaded instrument {instrument=}')
    except InstrumentError as e:
        logger.error('Error trying to start instrument. Shutting down...')
        return

    # Start the stream and setup the instrument
    logger.info(f'started instrument... {script_path=} {instrument_name=}')
    while True:
        logger.info('reading messages...')
        if astrid_msgq_read(i.exmsgq, &msg) < 0:
        try:
            msg = instrument.get_message()
        except InstrumentError as e:
            print('There was a problem reading from the msg q. Maybe try turning it off and on again?')
            continue



@@ 722,19 721,24 @@ def _run_forever(str script_path, str instrument_name, int channels, stop_event)
            stop_event.set()
            break

        elif msg.type == LPMSG_UPDATE:
            logger.info('PY MSG: update')
            if stream_graph_update(instrument) < 0:
                logger.error('Error trying to schedule python render...')

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

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

        elif msg.type == LPMSG_LOAD:
            if instrument is None:
                instrument = _load_instrument(instrument_name, script_path)
                instrument = Instrument(instrument_name, script_path)
            else:
                last_edit = os.path.getmtime(instrument.path)
                if last_edit > instrument.last_reload:


@@ 743,10 747,10 @@ def _run_forever(str script_path, str instrument_name, int channels, stop_event)

    logger.info('python instrument shutting down...')

def run_forever(str script_path, str instrument_name=None, channels=2):
def run_forever(str script_path, str instrument_name=None, int channels=2, double adc_length=30):
    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 = Process(target=_run_forever, args=(script_path, instrument_name, channels, adc_length, stop_event))
    render_process.start()

    try:

M pippi/ugens.pxd => pippi/ugens.pxd +5 -2
@@ 4,14 4,16 @@ cdef extern from "pippicore.h":
    ctypedef double lpfloat_t

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels

        lpfloat_t phase
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lpbuffer_factory_t: 
        lpbuffer_t * (*create)(size_t, int, int)


@@ 199,3 201,4 @@ cdef class Node:
cdef class Graph:
    cdef dict nodes
    cdef object outputs
    cdef double next_sample(Graph self)

M pippi/ugens.pyx => pippi/ugens.pyx +21 -1
@@ 112,6 112,7 @@ cdef class Node:
            stack = []
            for b in value:
                if isinstance(b, str):
                    # translate the string into a libpippi constant
                    b = dsp.wt(b)
                stack += [dsp.buffer(b)] 
            stack = dsp.join(stack)


@@ 181,7 182,26 @@ cdef class Graph:

        self.nodes[anodename].connections[aportname] += [(bnodename, bportname, _mult, _add)]

    def render(self, double length, int samplerate=DEFAULT_SAMPLERATE, int channels=DEFAULT_CHANNELS):
    cdef double next_sample(Graph self):
        cdef double sample = 0

        # first process all the nodes
        for _, node in self.nodes.items():
            node.process()

            # connect the outputs to the inputs
            for portname, connections in node.connections.items():
                port = node.get_output(portname)
                for connode, conport, mult, add in connections:
                    value = port * mult + add
                    if connode == 'main' and conport == 'output':
                        sample += value
                    else:
                        self.nodes[connode].set_param(conport, value)

        return sample

    def render(Graph self, double length, int samplerate=DEFAULT_SAMPLERATE, int channels=DEFAULT_CHANNELS):
        cdef size_t framelength = <size_t>(length * samplerate)
        cdef double sample = 0
        cdef size_t i

M pippi/wavetables.pxd => pippi/wavetables.pxd +4 -2
@@ 4,14 4,16 @@ cdef extern from "pippicore.h":
    ctypedef double lpfloat_t

    ctypedef struct lpbuffer_t:
        lpfloat_t * data
        size_t length
        int samplerate
        int channels

        lpfloat_t phase
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    ctypedef struct lpwavetable_factory_t:
        lpbuffer_t * (*create)(const char * name, size_t length)

M tests/test_ugens.py => tests/test_ugens.py +1 -1
@@ 3,7 3,7 @@ from pippi import dsp, fx, ugens
import numpy as np

class TestUgens(TestCase):
    """ FIXME pulsar init is different now
    """ There's a better way to handle param updates...
    def test_ugen_pulsar(self):
        graph = ugens.Graph()
        graph.add_node('s1', 'sine', freq=100)