~hecanjog/pippi

57882e8aedd2e437c250aceb45bf1f48f41c70c0 — Erik Schoster 26 days ago f4a1e97 main
use frames for pq scheduling, add astrid autotrigger
M astrid/Makefile => astrid/Makefile +2 -10
@@ 29,7 29,7 @@ LPDBINCLUDES = -I${LPDIR}/vendor/sqlite3
LPFLAGS = -g -std=gnu2x -Wall -Wextra -pedantic -O0 -DNOPYTHON
LPLIBS = -lm -ldl -lpthread -lrt -ljack -lasound

default: astrid-sampler
default: build

clean:
	rm -rf build/*


@@ 101,14 101,6 @@ astrid-bufstr:
	echo "Building astrid bufstr...";
	$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/bufstr.c $(LPLIBS) -o build/astrid-bufstr

astrid-sampler:
	mkdir -p build

	echo "Building astrid sampler test programs...";
	$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/dubby.c $(LPLIBS) -o build/dubby

	#$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/sampler.c $(LPLIBS) -o build/astrid-sampler


astrid-paramset:
	mkdir -p build


@@ 164,7 156,7 @@ rolling:
	$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/rolling.c $(LPLIBS) -o build/rolling


build: clean astrid-q astrid-serial-tools astrid-ipc astrid-devices astrid-midimap astrid-pulsar astrid-simple
build: clean astrid-q astrid-serial-tools astrid-ipc

install: 
	cp build/astrid-* /usr/local/bin/

M astrid/src/astrid.c => astrid/src/astrid.c +208 -302
@@ 841,7 841,7 @@ int lpsampler_write_ringbuffer_block(

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



@@ 874,6 874,54 @@ int lpsampler_write_ringbuffer_block(
    return 0;
}

int lpsampler_overdub_ringbuffer_block(
        char * name, 
        lpbuffer_t * buf,
        float ** block, 
        int channels, 
        lpfloat_t volume,
        lpfloat_t feedback,
        size_t blocksize_in_frames
    ) {
    size_t insert_pos, i, boundry;
    int c;
    float * channelp;
    float sample = 0;

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

    boundry = buf->length * channels;

    /* Copy the block */
    for(c=0; c < channels; c++) {
        channelp = block[c];
        for(i=0; i < blocksize_in_frames; i++) {
            insert_pos = ((buf->pos+i) * channels + c) % boundry;
            sample = *channelp++;
            buf->data[insert_pos] += (lpfilternan(sample) * volume) + (buf->data[insert_pos] * feedback);
        }
    }

    /* Increment the write position */
    buf->pos += blocksize_in_frames;
    while(buf->pos >= buf->length) {
        buf->pos -= buf->length;
    }

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

    return 0;
}


int lpsampler_read_ringbuffer_block(
        char * name, 
        lpbuffer_t * buf,


@@ 1018,224 1066,6 @@ int lpserial_getctl(int device_id, int ctl, lpfloat_t * value) {
    return 0;
}


/* MIDI trigger maps for noteon 
 * (and eventually cc triggers)
 * ***************************/

int lpmidi_add_msg_to_notemap(int device_id, int note, lpmsg_t msg) {
    int fd, bytes_written;
    size_t notemap_path_length;
    char * notemap_path;

    notemap_path_length = snprintf(NULL, 0, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note) + 1;
    notemap_path = (char *)calloc(1, notemap_path_length);
    snprintf(notemap_path, notemap_path_length, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note);

    if((fd = open(notemap_path, O_RDWR | O_CREAT | O_APPEND, LPIPC_PERMS)) < 0) {
        syslog(LOG_ERR, "Could not open file for appending. Error: %s\n", strerror(errno));
        return -1;
    }

    if((bytes_written = write(fd, &msg, sizeof(lpmsg_t))) < 0) {
        syslog(LOG_ERR, "Could not append msg to file. Error: %s\n", strerror(errno));
        return -1;
    }

    if(bytes_written != sizeof(lpmsg_t)) {
        syslog(LOG_ERR, "Wrote incorrect number of bytes to notemap. Expected %d, wrote %d\n", (int)sizeof(lpmsg_t), bytes_written);
        return -1;
    }

    if(close(fd) < 0) {
        syslog(LOG_ERR, "Could not close notemap file after writing. Error: %s\n", strerror(errno));
        return -1;
    }

    free(notemap_path);

    return 0;
}

int lpmidi_remove_msg_from_notemap(int device_id, int note, int index_to_remove) {
    int fd, bytes_read, bytes_written, map_index;
    struct stat statbuf;
    size_t notemap_path_length, notemap_size, read_pos;
    char * notemap_path;
    lpmsg_t msg = {0};

    notemap_path_length = snprintf(NULL, 0, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note) + 1;
    notemap_path = (char *)calloc(1, notemap_path_length);
    snprintf(notemap_path, notemap_path_length, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note);

    if((fd = open(notemap_path, O_RDWR)) < 0) {
        syslog(LOG_ERR, "Could not open file for printing. Error: %s\n", strerror(errno));
        return -1;
    }

    if(fstat(fd, &statbuf) < 0) {
        syslog(LOG_ERR, "Could not stat notemap file. Error: %s\n", strerror(errno));
        return -1;
    }

    notemap_size = statbuf.st_size;
    map_index = 0;
    for(read_pos=0; read_pos < notemap_size; read_pos += sizeof(lpmsg_t)) {
        if((bytes_read = read(fd, &msg, sizeof(lpmsg_t))) < 0) {
            syslog(LOG_ERR, "Could not read msg from file during removal walk. Error: %s\n", strerror(errno));
            return -1;
        }

        if(map_index == index_to_remove) {
            msg.type = LPMSG_EMPTY;

            if(lseek(fd, read_pos, SEEK_SET) < 0) {
                syslog(LOG_ERR, "Could not rewind midimap to remove msg from file. Error: %s\n", strerror(errno));
                return -1;
            }

            if((bytes_written = write(fd, &msg, sizeof(lpmsg_t))) < 0) {
                syslog(LOG_ERR, "Could not remove msg from file. Error: %s\n", strerror(errno));
                return -1;
            }
        }

        map_index += 1;
    }

    if(close(fd) < 0) {
        syslog(LOG_ERR, "Could not close notemap file after msg removal. Error: %s\n", strerror(errno));
        return -1;
    }

    free(notemap_path);

    return 0;
}

int lpmidi_print_notemap(int device_id, int note) {
    int fd, bytes_read, map_index;
    struct stat statbuf;
    size_t notemap_path_length, notemap_size, read_pos;
    char * notemap_path;
    lpmsg_t msg = {0};

    notemap_path_length = snprintf(NULL, 0, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note) + 1;
    notemap_path = (char *)calloc(1, notemap_path_length);
    snprintf(notemap_path, notemap_path_length, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note);

    if((fd = open(notemap_path, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Could not open file for printing. Error: %s\n", strerror(errno));
        return -1;
    }

    if(fstat(fd, &statbuf) < 0) {
        syslog(LOG_ERR, "Could not stat notemap file. Error: %s\n", strerror(errno));
        return -1;
    }

    notemap_size = statbuf.st_size;
    map_index = 0;
    for(read_pos=0; read_pos < notemap_size; read_pos += sizeof(lpmsg_t)) {
        if((bytes_read = read(fd, &msg, sizeof(lpmsg_t))) < 0) {
            syslog(LOG_ERR, "Could not read msg from file. Error: %s\n", strerror(errno));
            return -1;
        }

        printf("\nmap_index: %d\n", map_index);
        printf("bytes_read: %d, read_pos: %ld, notemap_size: %ld\n", bytes_read, read_pos, notemap_size);
        printf("msg.type: %d msg.initiated: %f msg.instrument_name: %s\n", msg.type, msg.initiated, msg.instrument_name);
        if(msg.type == LPMSG_EMPTY) {
            printf("this message is empty!\n");
        }

        map_index += 1;
    }

    if(close(fd) < 0) {
        syslog(LOG_ERR, "Could not close notemap file after reading. Error: %s\n", strerror(errno));
        return -1;
    }

    free(notemap_path);

    return 0;
}

int lpmidi_trigger_notemap(int device_id, int note) {
    int fd, bytes_read;
    struct stat statbuf;
    size_t notemap_path_length, notemap_size, read_pos;
    char * notemap_path;
    double now = 0;
    lpmsg_t msg = {0};

    notemap_path_length = snprintf(NULL, 0, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note) + 1;
    notemap_path = (char *)calloc(1, notemap_path_length);
    snprintf(notemap_path, notemap_path_length, ASTRID_MIDIMAP_NOTEBASE_PATH, device_id, note);

    if(access(notemap_path, F_OK) < 0) {
        syslog(LOG_DEBUG, "No notemap for %s\n", notemap_path);
        return 0;
    }

    if((fd = open(notemap_path, O_RDWR)) < 0) {
        syslog(LOG_ERR, "Could not open notemap file for triggering. Error: %s\n", strerror(errno));
        return -1;
    }

    if(fstat(fd, &statbuf) < 0) {
        syslog(LOG_ERR, "Could not stat notemap file for triggering. Error: %s\n", strerror(errno));
        return -1;
    }

    notemap_size = statbuf.st_size;
    for(read_pos=0; read_pos < notemap_size; read_pos += sizeof(lpmsg_t)) {
        if((bytes_read = read(fd, &msg, sizeof(lpmsg_t))) < 0) {
            syslog(LOG_ERR, "Could not read msg from file during notemap trigger walk. Error: %s\n", strerror(errno));
            return -1;
        }

        if(msg.type == LPMSG_EMPTY) continue;

        if(lpscheduler_get_now_seconds(&now) < 0) {
            syslog(LOG_ERR, "Could not get now seconds during notemap trigger. Error: %s\n", strerror(errno));
            return -1;
        }

        msg.initiated = now;

        syslog(LOG_INFO, "Sending message from lpmidi trigger notemap\ninitiated %f\nscheduled %f\ninterval %f\noffset %f\nmax_processing_time %f\nonset_delay %ld\nvoice_id %ld\ncount %ld\ntype %d\nmsg %s\nname %s\n\n", 
            msg.initiated,
            msg.scheduled,
            msg.interval,
            msg.offset,
            msg.max_processing_time,
            msg.onset_delay,
            msg.voice_id, 
            msg.count,
            msg.type,
            msg.msg,
            msg.instrument_name
        );

        if(send_message(msg.instrument_name, msg) < 0) {
            syslog(LOG_ERR, "Could not schedule msg for sending during notemap trigger. Error: %s\n", strerror(errno));
            return -1;
        }
        syslog(LOG_INFO, "Sent! lpmidi trigger notemap message\n");
    }

    if(close(fd) < 0) {
        syslog(LOG_ERR, "Could not close notemap file after triggering. Error: %s\n", strerror(errno));
        return -1;
    }

    free(notemap_path);

    return 0;
}

int lpmidi_relay_to_instrument(char * instrument_name, unsigned char mtype, unsigned char mid, unsigned char mval) {
    lpmsg_t msg = {0};
    size_t offset = 0;


@@ 2251,6 2081,28 @@ int lpscheduler_get_now_seconds(double * now) {
    return 0;
}

int lpscheduler_get_now_ticks(size_t * now, int samplerate) {
    // ticks == frames @ given samplerate
    clockid_t cid;
    struct timespec ts;

#if defined(__linux__)
    cid = CLOCK_MONOTONIC_RAW;
#else
    cid = CLOCK_MONOTONIC;
#endif

    if(clock_gettime(cid, &ts) < 0) {
        syslog(LOG_ERR, "scheduler_get_now_seconds: clock_gettime error: %s\n", strerror(errno));
        return -1; 
    }

    *now = (size_t)ts.tv_sec * samplerate;
    *now += (size_t)(((size_t)ts.tv_nsec * samplerate) * 1e-9);

    return 0;
}

void scheduler_get_now(struct timespec * now) {
    clock_gettime(CLOCK_MONOTONIC_RAW, now);
}


@@ 2369,7 2221,7 @@ static inline void start_playing(lpscheduler_t * s, lpevent_t * e) {
    lpevent_t * current;
    lpevent_t * prev;

    syslog(LOG_INFO, "START playing event ID %ld\n", e->id);
    syslog(LOG_DEBUG, "START playing event ID %ld\n", e->id);

    /* Remove from the waiting queue */
    if(s->waiting_queue_head == NULL) {


@@ 2406,7 2258,7 @@ static inline void stop_playing(lpscheduler_t * s, lpevent_t * e) {
    lpevent_t * current;
    lpevent_t * prev;

    syslog(LOG_INFO, "STOP playing event ID %ld\n", e->id);
    syslog(LOG_DEBUG, "STOP playing event ID %ld\n", e->id);

    /* Remove from the playing stack */
    if(s->playing_stack_head == NULL) {


@@ 2477,18 2329,21 @@ lpscheduler_t * scheduler_create(int realtime, int channels, lpfloat_t samplerat

/* look for events waiting to be scheduled */
static inline void scheduler_update(lpscheduler_t * s) {
    size_t now_ticks = 0;
    lpevent_t * current;
    void * next;

    lpscheduler_get_now_ticks(&now_ticks, s->samplerate);
    if(s->waiting_queue_head != NULL) {
        current = s->waiting_queue_head;
        while(current->next != NULL) {
            next = current->next;
            if(s->ticks >= current->onset) {
            if(now_ticks >= current->onset) {
                start_playing(s, current);
            }
            current = (lpevent_t *)next;
        }
        if(s->ticks >= current->onset) {
        if(now_ticks >= current->onset) {
            start_playing(s, current);
        }
    }


@@ 2592,14 2447,13 @@ void lpscheduler_tick(lpscheduler_t * s) {

    /* Increment process ticks and update now timestamp */
    s->ticks += 1;
    if(s->realtime == 1) {
        scheduler_get_now(s->now);
    } else {
    if(s->realtime == 0) {
        scheduler_increment_timespec_by_ns(s->now, s->tick_ns);
    }
}

void scheduler_schedule_event(lpscheduler_t * s, lpbuffer_t * buf, size_t onset_delay) {
    size_t now_ticks = 0;
    lpevent_t * e;

    e = (lpevent_t *)LPMemoryPool.alloc(1, sizeof(lpevent_t));


@@ 2610,10 2464,12 @@ void scheduler_schedule_event(lpscheduler_t * s, lpbuffer_t * buf, size_t onset_

    e->buf = buf;
    e->pos = 0;
    e->onset = s->ticks + onset_delay;
    //e->onset = s->ticks + onset_delay;
    lpscheduler_get_now_ticks(&now_ticks, s->samplerate);
    e->onset = now_ticks + onset_delay;

    syslog(LOG_INFO, "scheduling event ID %ld\n", e->id);
    syslog(LOG_INFO, "   with onset %ld\n", e->onset);
    syslog(LOG_DEBUG, "scheduling event ID %ld\n", e->id);
    syslog(LOG_DEBUG, "   with onset %ld\n", e->onset);

    start_waiting(s, e);
}


@@ 2692,14 2548,12 @@ int scheduler_cleanup_nursery(lpscheduler_t * s) {
    current = s->nursery_head;
    while(current->next != NULL) {
        next = (lpevent_t *)current->next;
        syslog(LOG_INFO, "freeing event ID %ld\n", current->id);
        LPBuffer.destroy(current->buf);
        free(current);
        current = next;        
    }

    if(current != NULL) {
        syslog(LOG_INFO, "freeing event ID %ld\n", current->id);
        LPBuffer.destroy(current->buf);
        free(current);
    }


@@ 2719,13 2573,16 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
    lpinstrument_t * instrument = (lpinstrument_t *)arg;
    float * output_channels[instrument->output_channels];
    float * input_channels[instrument->input_channels];
    lpfloat_t volume;
    size_t i;
    lpfloat_t volume, now=0;
    size_t i, offset_in_ticks, period_in_ticks, trigger_reset,
           last_trigger_time, next_trigger_time, delay_from_now_ticks,
           block_start, block_end, now_ticks=0;
    int c;

    if(!instrument->is_running) return 0;

    syslog(LOG_DEBUG, "JACK CALLBACK: Begin. input_channels=%d output_channels=%d nframes=%ld %s\n", instrument->input_channels, instrument->output_channels, (size_t)nframes, instrument->name);
    lpscheduler_get_now_ticks(&now_ticks, instrument->samplerate);
    lpscheduler_get_now_seconds(&now);

    if(!instrument->has_been_initialized) {
        syslog(LOG_DEBUG, "Seeding the random number generator from the audio callback. %s\n", instrument->name);


@@ 2735,7 2592,6 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
        // or is there a reason to have mainthread-before AND audiothread-before?
    }

    syslog(LOG_DEBUG, "Zeroing output and getting channel buffers. %s\n", instrument->name);
    for(c=0; c < instrument->input_channels; c++) {
        input_channels[c] = (float *)jack_port_get_buffer(instrument->inports[c], nframes);
    }


@@ 2745,14 2601,12 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
    }

    /* write the block into the adc ringbuffer */
    syslog(LOG_DEBUG, "Writing block to adc ringbuffer. %s\n", instrument->name);
    if(lpsampler_write_ringbuffer_block(instrument->adcname, instrument->adcbuf, input_channels, instrument->input_channels, nframes) < 0) {
        syslog(LOG_ERR, "Error writing into adc ringbuf\n");
        return 0;
    }

    /* mix in async renders */
    syslog(LOG_DEBUG, "Mixing async renders. %s\n", instrument->name);
    for(i=0; i < (size_t)nframes; i++) {
        lpscheduler_tick(instrument->async_mixer);
        for(c=0; c < instrument->output_channels; c++) {


@@ 2760,11 2614,7 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
        }
    }

    syslog(LOG_DEBUG, "Checking stream callback. %s\n", instrument->name);
    if(instrument->stream != NULL) {
        syslog(LOG_DEBUG, "Mixing stream callback. %s\n", instrument->name);
        syslog(LOG_DEBUG, "Mixing stream callback. input=%f\n", input_channels[0][0]);
        syslog(LOG_DEBUG, "Mixing stream callback. output=%f\n", output_channels[0][0]);
        if(instrument->stream((size_t)nframes, input_channels, output_channels, (void *)instrument) < 0) {
            return -1;
        }


@@ 2773,7 2623,6 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
    /* write the output block into the resampler ringbuffer */
    if(astrid_instrument_get_param_int32(instrument, instrument->resampler_param_rec, 1) == 1) {
        /* TODO: support overdub with feedback, toggling rec on/off */
        syslog(LOG_DEBUG, "Writing output to resampler ringbuffer. %s\n", instrument->name);
        if(lpsampler_write_ringbuffer_block(instrument->resamplername, instrument->resamplerbuf, output_channels, instrument->output_channels, nframes) < 0) {
            syslog(LOG_ERR, "Error writing into resampler ringbuf\n");
            return 0;


@@ 2782,27 2631,88 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {

    /* clamp output and apply post fader volume  */
    volume = astrid_instrument_get_param_float(instrument, instrument->param_volume, instrument->initial_volume);
    syslog(LOG_DEBUG, "Clamping output w/volume %f. %s\n", volume, instrument->name);
    for(c=0; c < instrument->output_channels; c++) {
        for(i=0; i < (size_t)nframes; i++) {
            output_channels[c][i] = fmax(-1.f, fmin(output_channels[c][i] * volume, 1.f));
        }
    }

    syslog(LOG_DEBUG, "JACK CALLBACK: Done. %s\n", instrument->name);
    /* if autotrigger is engaged, see if we need to send a trigger */
    if(instrument->autotrigger_is_enabled) {
        block_start = now_ticks;
        block_end = block_start + nframes;

        last_trigger_time = astrid_instrument_get_param_int32(instrument, instrument->param_last_trigger_time, now_ticks);
        period_in_ticks = astrid_instrument_get_param_int32(instrument, instrument->param_period_in_ticks, instrument->samplerate);
        offset_in_ticks = astrid_instrument_get_param_int32(instrument, instrument->param_offset_in_ticks, 0);
        trigger_reset = astrid_instrument_get_param_int32(instrument, instrument->param_trigger_reset, 0);


        if(trigger_reset != 0) {
            instrument->autotrigger_msg.count = 0;

            // set the offset to align the phase to 0 at now
            astrid_instrument_set_param_int32(instrument, instrument->param_offset_in_ticks, 0);
            offset_in_ticks = 0;

            // update last trigger time, send the trigger and unset the reset flag
            astrid_instrument_set_param_int32(instrument, instrument->param_last_trigger_time, now_ticks);
            astrid_instrument_set_param_int32(instrument, instrument->param_trigger_reset, 0);
        } 

        next_trigger_time = last_trigger_time + period_in_ticks + offset_in_ticks;
        instrument->autotrigger_msg.initiated = now_ticks;

        //if(next_trigger_time < now_ticks) next_trigger_time = now_ticks;

        c = 0;
        while(next_trigger_time < block_end && c < 10) {
            c += 1;
            delay_from_now_ticks = next_trigger_time - now_ticks;
#if 0
            syslog(LOG_INFO, "%s block_end=%ld\n", instrument->name, block_end);
            syslog(LOG_INFO, "%s now_ticks=%ld\n", instrument->name, now_ticks);
            syslog(LOG_INFO, "%s last_trigger_time=%ld\n", instrument->name, last_trigger_time);
            syslog(LOG_INFO, "%s next_trigger_time=%ld\n", instrument->name, next_trigger_time);
            syslog(LOG_INFO, "%s period_in_ticks=%ld\n", instrument->name, period_in_ticks);
            syslog(LOG_INFO, "%s offset_in_ticks=%ld\n", instrument->name, offset_in_ticks);
            syslog(LOG_INFO, "%s delay_from_now_ticks=%ld\n", instrument->name, delay_from_now_ticks);
#endif
            instrument->autotrigger_msg.scheduled = (lpfloat_t)delay_from_now_ticks / (lpfloat_t)instrument->samplerate;
            if(relay_message_to_seq(instrument, instrument->autotrigger_msg) < 0) {
                syslog(LOG_ERR, "%s renderer: Could not read relay message to seq. Error: (%d) %s\n", instrument->name, errno, strerror(errno));
            }

            last_trigger_time = next_trigger_time;
            astrid_instrument_set_param_int32(instrument, instrument->param_last_trigger_time, last_trigger_time);
            next_trigger_time += period_in_ticks;
        }

        //next_trigger_time = last_trigger_time + period_in_ticks + offset_in_ticks;

        /*
        if(now_ticks >= next_trigger_time) {
            instrument->autotrigger_msg.count += 1;
            send_play_message(instrument->autotrigger_msg);
            astrid_instrument_set_param_int32(instrument, instrument->param_last_trigger_time, now_ticks);
        }
        */
    }


    return 0;
}

/* instrument seq priority queue callbacks */
static int msgpq_cmp_pri(double next, double curr) {
static int msgpq_cmp_pri(size_t next, size_t curr) {
    return (next > curr);
}

static double msgpq_get_pri(void * a) {
static size_t msgpq_get_pri(void * a) {
    return ((lpmsgpq_node_t *)a)->timestamp;
}

static void msgpq_set_pri(void * a, double timestamp) {
static void msgpq_set_pri(void * a, size_t timestamp) {
    ((lpmsgpq_node_t *)a)->timestamp = timestamp;
}



@@ 2843,7 2753,7 @@ void * instrument_seq_pq(void * arg) {
    lpmsg_t * msg;
    lpmsgpq_node_t * node;
    void * d;
    double now;
    size_t now = 0;

    now = 0;
    syslog(LOG_DEBUG, ":::: INSTRUMENT SEQ PQ STARTING ::::\n");


@@ 2871,7 2781,7 @@ void * instrument_seq_pq(void * arg) {
        }

        /* Get now */
        if(lpscheduler_get_now_seconds(&now) < 0) {
        if(lpscheduler_get_now_ticks(&now, instrument->samplerate) < 0) {
            syslog(LOG_CRIT, "Error getting now in message scheduler\n");
            exit(1);
        }


@@ 2903,7 2813,7 @@ void * instrument_seq_pq(void * arg) {

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

    syslog(LOG_DEBUG, "SEQ PQ MSG: got a scheduled message to insert into the sequencer priority queue\n");
    syslog(LOG_DEBUG, "SEQ PQ MSG: pqnode_index=%d\n", instrument->pqnode_index);


@@ 2913,17 2823,17 @@ int relay_message_to_seq(lpinstrument_t * instrument, lpmsg_t msg) {
    instrument->pqnode_index += 1;
    while(instrument->pqnode_index >= NUM_NODES) instrument->pqnode_index -= NUM_NODES;

    if(lpscheduler_get_now_seconds(&now) < 0) {
    if(lpscheduler_get_now_ticks(&now, instrument->samplerate) < 0) {
        syslog(LOG_CRIT, "SEQ PQ MSG: 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 = msg.scheduled - (msg.max_processing_time * 2);
    seq_delay = (msg.scheduled * instrument->samplerate) - (msg.max_processing_time * 2);
    d->timestamp = msg.initiated + seq_delay;

    syslog(LOG_DEBUG, "d->timestamp=%f msg.scheduled=%f msg.initiated=%f\n",
    syslog(LOG_DEBUG, "d->timestamp=%ld msg.scheduled=%f msg.initiated=%ld\n",
                    d->timestamp, msg.scheduled, msg.initiated);

    // Remove the scheduled flag before relaying the message


@@ 2960,8 2870,6 @@ void * instrument_message_thread(void * arg) {
    //double processing_time_so_far, onset_delay_in_seconds, now=0;
    lpinstrument_t * instrument = (lpinstrument_t *)arg;
    int is_scheduled = 0;
    double now = 0.f;
    size_t onset_delay, ticks, grid_interval=0;

    instrument->is_waiting = 1;
    while(instrument->is_running) {


@@ 2972,14 2880,12 @@ void * instrument_message_thread(void * arg) {
        }

        is_scheduled = ((instrument->msg.flags & LPFLAG_IS_SCHEDULED) == LPFLAG_IS_SCHEDULED);
        syslog(LOG_INFO, "C MSG: name=%s\n", instrument->msg.instrument_name);
        syslog(LOG_INFO, "C MSG: scheduled=%f\n", instrument->msg.scheduled);
        syslog(LOG_INFO, "C MSG: interval=%f\n", instrument->msg.interval);
        syslog(LOG_INFO, "C MSG: offset=%f\n", instrument->msg.offset);
        syslog(LOG_INFO, "C MSG: voice_id=%d\n", (int)instrument->msg.voice_id);
        syslog(LOG_INFO, "C MSG: type=%d\n", (int)instrument->msg.type);
        syslog(LOG_INFO, "C MSG: flags=%d\n", (int)instrument->msg.flags);
        syslog(LOG_INFO, "C MSG: is_scheduled=%d\n", is_scheduled);
        syslog(LOG_DEBUG, "C MSG: name=%s\n", instrument->msg.instrument_name);
        syslog(LOG_DEBUG, "C MSG: scheduled=%f\n", instrument->msg.scheduled);
        syslog(LOG_DEBUG, "C MSG: voice_id=%d\n", (int)instrument->msg.voice_id);
        syslog(LOG_DEBUG, "C MSG: type=%d\n", (int)instrument->msg.type);
        syslog(LOG_DEBUG, "C MSG: flags=%d\n", (int)instrument->msg.flags);
        syslog(LOG_DEBUG, "C MSG: is_scheduled=%d\n", is_scheduled);

        // Handle shutdown early
        if(instrument->msg.type == LPMSG_SHUTDOWN) {


@@ 3030,19 2936,7 @@ void * instrument_message_thread(void * arg) {
                }

                /* Schedule the buffer for playback */
                lpscheduler_get_now_seconds(&now);
                onset_delay = 0;
                if(bufmsg.interval > 0) {
                    ticks = (size_t)(now * instrument->samplerate);
                    grid_interval = (size_t)(bufmsg.interval * instrument->samplerate);
                    onset_delay = grid_interval - (ticks % grid_interval);
                }

                if(bufmsg.offset > 0) {
                    onset_delay += (size_t)(bufmsg.offset * instrument->samplerate);
                }

                scheduler_schedule_event(instrument->async_mixer, buf, onset_delay);
                scheduler_schedule_event(instrument->async_mixer, buf, 0);
                //scheduler_debug(instrument->async_mixer);
                break;



@@ 3413,7 3307,7 @@ handle_serial_message_events:

void * instrument_midi_listener_thread(void * arg) {
    snd_seq_t * seq_handle;
    snd_seq_event_t * event;
    snd_seq_event_t * event = NULL;
    int ret, port;

    lpinstrument_t * instrument = (lpinstrument_t *)arg;


@@ 3443,6 3337,13 @@ void * instrument_midi_listener_thread(void * arg) {

    while(instrument->is_running) {
        snd_seq_event_input(seq_handle, &event);

        if(event == NULL) {
            syslog(LOG_ERR, "%s midi listener: Got NULL event, skipping...\n", instrument->name);
            usleep((useconds_t)10000);
            continue;
        }

        switch(event->type) {
            case SND_SEQ_EVENT_NOTEON:
                lpmidi_relay_to_instrument(instrument->name, NOTE_ON, event->data.note.note, event->data.note.velocity);


@@ 3566,12 3467,11 @@ lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t confi
    int c=0;
    const char ** ports;
    int port_count=0;
    char hash_key[PATH_MAX] = {0};

    instrument = (lpinstrument_t *)LPMemoryPool.alloc(1, sizeof(lpinstrument_t));
    memset(instrument, 0, sizeof(lpinstrument_t));

    setlogmask(LOG_UPTO(LOG_ERR));
    setlogmask(LOG_UPTO(LOG_INFO));
    openlog(config.name, LOG_PID, LOG_USER);
    syslog(LOG_DEBUG, "starting %s instrument...\n", config.name);



@@ 3588,6 3488,7 @@ lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t confi
    instrument->trigger = config.trigger_callback;
    instrument->ext_relay_enabled = config.ext_relay_enabled;
    instrument->is_ready = 1;
    instrument->autotrigger_is_enabled = config.autotrigger_is_enabled;

    instrument->midiin_device_id = -1;
    if(config.midiin_device_name != NULL) {


@@ 3676,6 3577,12 @@ lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t confi
    /* init scheduler */
    instrument->async_mixer = scheduler_create(1, instrument->output_channels, instrument->samplerate);

    /* Prepare the message structs */ 
    init_instrument_message(&instrument->msg, instrument->name);
    init_instrument_message(&instrument->cmd, instrument->name);
    init_instrument_message(&instrument->autotrigger_msg, instrument->name);
    instrument->autotrigger_msg.type = LPMSG_TRIGGER;

    /* Set the main jack callback which always runs: maybe there is an analysis-only use to support too? */
    jack_set_process_callback(instrument->jack_client, astrid_instrument_jack_callback, (void *)instrument);
    for(c=0; c < instrument->output_channels; c++) {


@@ 3747,34 3654,28 @@ lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t confi
    /* Create the internal ringbuffer for sampling from the adc */
    snprintf(instrument->adcname, PATH_MAX, "%s-adc", instrument->name);
    if((instrument->adcbuf = lpsampler_create(instrument->adcname, config.adc_length, instrument->input_channels, instrument->samplerate)) == NULL) {
        syslog(LOG_INFO, "Could not create instrument ADC buffer\n");
        syslog(LOG_CRIT, "Could not create instrument ADC buffer\n");
        goto astrid_instrument_shutdown_with_error;
    }

    /* Create the internal ringbuffer for resampling */
    snprintf(instrument->resamplername, PATH_MAX, "%s-resampler", instrument->name);
    if((instrument->resamplerbuf = lpsampler_create(instrument->resamplername, config.resampler_length, instrument->output_channels, instrument->samplerate)) == NULL) {
        syslog(LOG_INFO, "Could not create instrument resampler buffer\n");
        syslog(LOG_CRIT, "Could not create instrument resampler buffer\n");
        goto astrid_instrument_shutdown_with_error;
    }

    /* Create (or open) mix busses if we've selected any */
    // TODO

    /* Initialize the built-in control param hashes */
    snprintf(hash_key, PATH_MAX, "%s-volume", instrument->name);
    instrument->param_volume = lphashstr(hash_key); 
    snprintf(hash_key, PATH_MAX, "%s-resampler-rec", instrument->name);
    instrument->resampler_param_rec = lphashstr(hash_key); 
    snprintf(hash_key, PATH_MAX, "%s-resampler-dub", instrument->name);
    instrument->resampler_param_dub = lphashstr(hash_key); 
    snprintf(hash_key, PATH_MAX, "%s-resampler-clear", instrument->name);
    instrument->resampler_param_clear = lphashstr(hash_key); 
    snprintf(hash_key, PATH_MAX, "%s-resampler-pos", instrument->name);
    instrument->resampler_param_pos = lphashstr(hash_key); 
    snprintf(hash_key, PATH_MAX, "%s-resampler-feedback", instrument->name);
    instrument->resampler_param_feedback = lphashstr(hash_key); 

    instrument->param_volume = lphashstr("volume"); 
    instrument->param_period_in_ticks = lphashstr("at-period"); // autotrigger params
    instrument->param_offset_in_ticks = lphashstr("at-offset"); 
    instrument->param_trigger_reset = lphashstr("at-reset"); 
    instrument->param_last_trigger_time = lphashstr("__at-last"); 
    instrument->resampler_param_rec = lphashstr("resampler-rec"); 
    instrument->resampler_param_dub = lphashstr("resampler-dub"); 
    instrument->resampler_param_clear = lphashstr("resampler-clear"); 
    instrument->resampler_param_pos = lphashstr("resampler-pos"); 
    instrument->resampler_param_feedback = lphashstr("resampler-feedback"); 

    /* Register that everything is running and ready */
    instrument->is_running = 1;


@@ 3801,10 3702,6 @@ lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t confi
        syslog(LOG_DEBUG, "Opened message relay queue for %s with fd %d\n", instrument->name, instrument->exmsgq);
    }

    /* Prepare the message structs */ 
    init_instrument_message(&instrument->msg, instrument->name);
    init_instrument_message(&instrument->cmd, instrument->name);

    /* Start the sequencer thread */
    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));


@@ 3848,7 3745,16 @@ lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t confi
    }

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

    /* call the post init callback if there is one */
    if(config.post_init_callback != NULL) {
        if(config.post_init_callback((void *)instrument) < 0) {
            syslog(LOG_ERR, "Post init callback failed. Error: %s\n", strerror(errno));
        }
    }

    return instrument;


M astrid/src/astrid.h => astrid/src/astrid.h +21 -19
@@ 83,7 83,7 @@
#define ASTRID_DEVICEID_PATH "/tmp/astrid_device_id"

typedef struct lpmsgpq_node_t {
    double timestamp;
    size_t timestamp;
    lpmsg_t msg;
    size_t pos;
    int index; /* index in node pool */


@@ 148,17 148,17 @@ typedef struct lpinstrument_config_t {
    int output_channels;
    lpfloat_t initial_volume;
    lpfloat_t requested_samplerate; // TODO use jack API to adjust samplerate here
    int mixbus_count;
    int mixbus_handles[64]; // who needs more than 64 busses?
    double adc_length;
    double resampler_length;
    lpfloat_t adc_length;
    lpfloat_t resampler_length;
    int ext_relay_enabled;
    int is_interactive;
    int autotrigger_is_enabled;
    void * ctx;
    char * tty;
    char * midiin_device_name;
    char * midiout_device_name;
    int (*stream_callback)(size_t blocksize, float ** input, float ** output, void * instrument);
    int (*post_init_callback)(void * instrument);
    int (*renderer_callback)(void * instrument);
    int (*update_callback)(void * instrument, char * key, char * val);
    int (*trigger_callback)(void * instrument);


@@ 176,8 176,8 @@ typedef struct lpinstrument_t {
    int has_been_initialized;
    lpfloat_t samplerate;

    int mixbus_count;
    int mixbus_handles[64]; // who needs more than 64 busses?
    volatile int autotrigger_is_enabled;
    lpmsg_t autotrigger_msg;

    // linenoise cmd buf
    struct linenoiseState cmdstate;


@@ 194,6 194,10 @@ typedef struct lpinstrument_t {
    char datapath[PATH_MAX]; 

    uint32_t param_volume;
    uint32_t param_trigger_reset;
    uint32_t param_period_in_ticks;
    uint32_t param_offset_in_ticks;
    uint32_t param_last_trigger_time;

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


@@ 275,6 279,7 @@ void lpscheduler_tick(lpscheduler_t * s);
lpscheduler_t * scheduler_create(int, int, lpfloat_t);
void scheduler_destroy(lpscheduler_t * s);
int lpscheduler_get_now_seconds(double * now);
int lpscheduler_get_now_ticks(size_t * now, int samplerate);
int scheduler_cleanup_nursery(lpscheduler_t * s);

ssize_t lpcounter_create(char * name);


@@ 354,18 359,6 @@ int lpmidi_getcc(int device_id, int cc);
int lpmidi_setnote(int device_id, int note, int velocity);
int lpmidi_getnote(int device_id, int note);

/* TODO notemaps should also probably be moved to LMDB. We want them 
 * to also be global / session-scope so that any event astrid knows 
 * about can be mapped to any valid astrid message.
 *
 * In other words, these really aren't MIDI event maps, they should 
 * just be general event-to-message(s) mappings.
 */
int lpmidi_add_msg_to_notemap(int device_id, int note, lpmsg_t msg);
int lpmidi_remove_msg_from_notemap(int device_id, int note, int index);
int lpmidi_print_notemap(int device_id, int note);
int lpmidi_trigger_notemap(int device_id, int note);

/* TODO same goes for serial values: store them in the LMDB session */
int lpserial_setctl(int device_id, int param_id, size_t value);
int lpserial_getctl(int device_id, int ctl, lpfloat_t * value);


@@ 386,6 379,15 @@ int lpsampler_aquire(char * name);
int lpsampler_release(char * name);
int lpsampler_read_ringbuffer_block(char * name, lpbuffer_t * buf, size_t offset_in_frames, lpbuffer_t * out);
int lpsampler_write_ringbuffer_block(char * name, lpbuffer_t * buf, float ** block, int channels, size_t blocksize_in_frames);
int lpsampler_overdub_ringbuffer_block(
        char * name, 
        lpbuffer_t * buf, 
        float ** block, 
        int channels, 
        lpfloat_t volume,
        lpfloat_t feedback, 
        size_t blocksize_in_frames
);
lpbuffer_t * lpsampler_create(char * name, double length_in_seconds, int channels, int samplerate);
int lpsampler_destroy(char * name);
int lpsampler_destroy_and_unmap(char * name, lpbuffer_t * buf);

M libpippi/src/pippiconstants.h => libpippi/src/pippiconstants.h +1 -1
@@ 68,7 68,7 @@
#define FNV1_32_MAGIC_NUMBER ((u_int32_t)0x811c9dc5)

#define LPMAXNAME 16
#define LPMAXMSG (PIPE_BUF - (sizeof(double) * 5) - (sizeof(size_t) * 3) - (sizeof(uint16_t) * 2) - LPMAXNAME)
#define LPMAXMSG (PIPE_BUF - (sizeof(double) * 2) - (sizeof(size_t) * 4) - (sizeof(uint16_t) * 2) - LPMAXNAME)
#define LPMAXPAT 512 - sizeof(size_t)

#define LPMAXCHANNELS 32 /* used for temp storage for channel mapping, mostly. can be bumped up. should be only limited by hardware, really. */

M libpippi/src/pippicore.c => libpippi/src/pippicore.c +42 -1
@@ 106,6 106,10 @@ lpfloat_t interpolate_linear_channel(lpbuffer_t* buf, lpfloat_t phase, int chann

lpbuffer_t * param_create_from_float(lpfloat_t value);
lpbuffer_t * param_create_from_int(int value);
lpsmoother_t param_smoother_create(lpfloat_t samplerate);
void param_smoother_snap_to_value(lpsmoother_t * smoother, lpfloat_t value);
void param_smoother_update_samplerate(lpsmoother_t * smoother, lpfloat_t samplerate);
lpfloat_t param_smooth(lpsmoother_t * s, lpfloat_t value);

lpbuffer_t * create_wavetable(int name, size_t length);
void destroy_wavetable(lpbuffer_t* buf);


@@ 133,7 137,7 @@ lpmemorypool_factory_t LPMemoryPool = { 0, 0, 0, memorypool_init, memorypool_cus
const lparray_factory_t LPArray = { create_array, create_array_from, destroy_array };
const lpbuffer_factory_t LPBuffer = { create_buffer, create_buffer_from_float, create_buffer_from_bytes, copy_buffer, clone_buffer, clear_buffer, split2_buffer, scale_buffer, min_buffer, max_buffer, mag_buffer, play_buffer, pan_stereo_buffer, mix_buffers, remix_buffer, remap_buffer, clip_buffer, cut_buffer, cut_into_buffer, varispeed_buffer, resample_buffer, multiply_buffer, scalar_multiply_buffer, add_buffers, scalar_add_buffer, subtract_buffers, scalar_subtract_buffer, divide_buffers, scalar_divide_buffer, concat_buffers, buffers_are_equal, buffers_are_close, dub_buffer, dub_into_buffer, dub_scalar, env_process, env_buffer, pad_buffer, taper_buffer, trim_buffer, fill_buffer, repeat_buffer, reverse_buffer, resize_buffer, plot_buffer, destroy_buffer };
const lpinterpolation_factory_t LPInterpolation = { interpolate_linear_pos, interpolate_linear_pos2, interpolate_linear, interpolate_linear_channel, interpolate_hermite_pos, interpolate_hermite };
const lpparam_factory_t LPParam = { param_create_from_float, param_create_from_int };
const lpparam_factory_t LPParam = { param_create_from_float, param_create_from_int, param_smoother_create, param_smooth, param_smoother_snap_to_value, param_smoother_update_samplerate };
const lpwavetable_factory_t LPWavetable = { create_wavetable, create_wavetable_stack, destroy_wavetable };
const lpwindow_factory_t LPWindow = { create_window, create_window_stack, destroy_window };
const lpringbuffer_factory_t LPRingBuffer = { ringbuffer_create, ringbuffer_fill, ringbuffer_read, ringbuffer_readinto, ringbuffer_writefrom, ringbuffer_write, ringbuffer_readone, ringbuffer_writeone, ringbuffer_dub, ringbuffer_destroy };


@@ 1737,6 1741,43 @@ lpbuffer_t * param_create_from_int(int value) {
    return param;
}

/* This smoother was ported from sndkit with 
 * minimal changes. Thanks, Paul! 
 *
 * The implementation is explained in detail here:
 *     https://pbat.ch/sndkit/smoother/
 */
lpsmoother_t param_smoother_create(lpfloat_t samplerate) {
    lpsmoother_t smoother = {0};
    smoother.smooth = 0.012f;
    smoother.psmooth = -1;
    smoother.onedsr = 1.f/samplerate;
    return smoother;
}

void param_smoother_update_samplerate(lpsmoother_t * smoother, lpfloat_t samplerate) {
    smoother->onedsr = 1.f/samplerate;
}

void param_smoother_snap_to_value(lpsmoother_t * smoother, lpfloat_t value) {
    smoother->y0 = value;
}

lpfloat_t param_smooth(lpsmoother_t * s, lpfloat_t value) {
    lpfloat_t out = 0.f;

    if(s->psmooth != s->smooth) {
        s->a1 = pow(0.5f, s->onedsr/s->smooth);
        s->b0 = 1.f - s->a1;
        s->psmooth = s->smooth;
    }

    s->y0 = s->b0 * value + s->a1 * s->y0;
    out = s->y0;

    return out;
}

/* Interpolation
 * */
lpfloat_t interpolate_hermite(lpbuffer_t* buf, lpfloat_t phase) {

M libpippi/src/pippicore.h => libpippi/src/pippicore.h +4 -0
@@ 156,6 156,10 @@ typedef struct lpringbuffer_factory_t {
typedef struct lpparam_factory_t {
    lpbuffer_t * (*from_float)(lpfloat_t);
    lpbuffer_t * (*from_int)(int);
    lpsmoother_t (*create_smoother)(lpfloat_t samplerate);
    lpfloat_t (*smooth)(lpsmoother_t * s, lpfloat_t value);
    void (*snap_to_value)(lpsmoother_t * s, lpfloat_t value);
    void (*update_samplerate)(lpsmoother_t * s, lpfloat_t samplerate);
} lpparam_factory_t;

typedef struct lpmemorypool_factory_t {

M libpippi/src/pippitypes.h => libpippi/src/pippitypes.h +21 -21
@@ 25,13 25,6 @@ typedef struct lpbuffer_t {
// but included in pippicore for embedded use and 
// external messaging support.
typedef struct lpmsg_t {
    /* Timestamp when the message was initiated. 
     *
     * This is assigned at the same time that the 
     * voice ID is assigned to the message sequence.
     * */
    double initiated;     

    /* Relative delay for target completion time 
     * from initiation time.
     *


@@ 40,19 33,6 @@ typedef struct lpmsg_t {
     * */
    double scheduled;     

    /* Optional grid quantization values for renders.
     *
     * `interval` in seconds specifies the grid quantization 
     * as an interval while `offset` sets the phase offset.
     *
     * A 1 second interval will add a delay to the onset time 
     * for renders so that the event arrives only every N ticks 
     * where N=samplerate * interval, offset by X ticks where 
     * X=samplerate * offset.
     * */
    double interval;
    double offset;     

    /* The longest a message in this sequence has taken 
     * so far to be processed and reach completion.
     *


@@ 71,8 51,16 @@ typedef struct lpmsg_t {
     * */
    double max_processing_time;     

    /* Timestamp when the message was initiated in frames/ticks. 
     *
     * Always get this with lpscheduler_get_now_ticks
     *
     * This is the time when the message was originally sent.
     * */
    size_t initiated;     

    /* Time of arrival at mixer minus scheduled target
     * time rounded to nearest frame */
     * time rounded to nearest frame FIXME no, it isn't */
    size_t onset_delay;   

    /* The voice ID is also a message sequence ID */


@@ 102,6 90,18 @@ typedef struct lppatternbuf_t {
    unsigned char pattern[LPMAXPAT];
} lppatternbuf_t;

/* This smoother was ported from sndkit with 
 * minimal changes. Thanks, Paul! 
 *
 * The implementation is explained in detail here:
 *     https://pbat.ch/sndkit/smoother/
 */
typedef struct lpsmoother_t {
    lpfloat_t smooth;
    lpfloat_t a1, b0, y0, psmooth;
    lpfloat_t onedsr;
} lpsmoother_t;


/* This filter type is shared among the butterworth 
 * filters ported from Paul Batchelor's Soundpipe.

M libpippi/vendor/libpqueue/src/pqueue.h => libpippi/vendor/libpqueue/src/pqueue.h +1 -1
@@ 36,7 36,7 @@
#define PQUEUE_H

/** priority data type */
typedef double pqueue_pri_t;
typedef size_t pqueue_pri_t;

/** callback functions to get/set/compare the priority of an element */
typedef pqueue_pri_t (*pqueue_get_pri_f)(void *a);

M pippi/renderer.pxd => pippi/renderer.pxd +83 -53
@@ 110,11 110,9 @@ cdef extern from "astrid.h":
        NUM_LPMESSAGETYPES

    ctypedef struct lpmsg_t:
        double initiated
        double scheduled 
        double interval
        double offset
        double max_processing_time
        size_t initiated
        size_t onset_delay
        size_t voice_id
        size_t count


@@ 136,55 134,86 @@ cdef extern from "astrid.h":
        int channel

    ctypedef struct lpinstrument_config_t:
        char * name
        int input_channels
        int output_channels
        lpfloat_t initial_volume
        lpfloat_t requested_samplerate
        int mixbus_count
        int mixbus_handles[64]
        double adc_length
        double resampler_length
        int ext_relay_enabled
        int is_interactive
        void * ctx
        char * tty
        char * midiin_device_name
        char * midiout_device_name
        int (*stream_callback)(size_t blocksize, float ** input, float ** output, void * instrument)
        int (*renderer_callback)(void * instrument)
        int (*update_callback)(void * instrument, char * key, char * val)
        int (*trigger_callback)(void * instrument)
        char * name;
        int input_channels;
        int output_channels;
        lpfloat_t initial_volume;
        lpfloat_t requested_samplerate;
        lpfloat_t adc_length;
        lpfloat_t resampler_length;
        int ext_relay_enabled;
        int is_interactive;
        int autotrigger_is_enabled;
        void * ctx;
        char * tty;
        char * midiin_device_name;
        char * midiout_device_name;
        int (*stream_callback)(size_t blocksize, float ** input, float ** output, void * instrument);
        int (*post_init_callback)(void * instrument);
        int (*renderer_callback)(void * instrument);
        int (*update_callback)(void * instrument, char * key, char * val);
        int (*trigger_callback)(void * instrument);

    ctypedef struct lpinstrument_t:
        const char * name
        int channels
        int input_channels
        int output_channels
        int is_interactive
        volatile int is_running
        volatile int is_waiting
        volatile int is_ready
        int has_been_initialized
        lpfloat_t samplerate

        int mixbus_count
        int mixbus_handles[64]


        char adcname[4096]
        lpbuffer_t * adcbuf
        char resamplername[4096]
        lpbuffer_t * resamplerbuf

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

        lpscheduler_t * async_mixer
        char * name;
        int input_channels;
        int output_channels;
        int is_interactive;
        lpfloat_t initial_volume;
        volatile int is_running;
        volatile int is_waiting;
        volatile int is_ready;
        int has_been_initialized;
        lpfloat_t samplerate;

        volatile int autotrigger_is_enabled;
        lpmsg_t autotrigger_msg;

        char cmdbuf[1024];

        char datapath[4096]; 

        uint32_t param_volume;
        uint32_t param_trigger_reset;
        uint32_t param_period_in_ticks;
        uint32_t param_offset_in_ticks;
        uint32_t param_last_trigger_time;

        char adcname[4096];
        lpbuffer_t * adcbuf;
        char resamplername[4096];
        lpbuffer_t * resamplerbuf;
        uint32_t resampler_param_rec, \
                resampler_param_dub, \
                 resampler_param_pos, \
                 resampler_param_clear, \
                 resampler_param_feedback;

        char qname[NAME_MAX]; 
        char external_relay_name[NAME_MAX]; 
        char serial_message_q_name[NAME_MAX]; 
        char midiout_message_q_name[NAME_MAX]; 
        int ext_relay_enabled;
        int msgq;
        int exmsgq;
        int serialmsgq;
        int midioutmsgq;

        lpmsg_t msg;
        lpmsg_t cmd;

        int midiin_device_id;
        int midiout_device_id;

        int tty_is_enabled;
        char tty_path[NAME_MAX]; 

        void * context;
        int (*trigger)(void * instrument);
        int (*update)(void * instrument, char * key, char * val);
        int (*renderer)(void * instrument);
        int (*stream)(size_t blocksize, float ** input, float ** output, void * instrument);
        void (*shutdown)(int sig);

    lpbuffer_t * lpsampler_aquire_and_map(char * name);
    int lpsampler_release_and_unmap(char * name, lpbuffer_t * buf);


@@ 229,6 258,7 @@ cdef extern from "astrid.h":

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

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


@@ 245,11 275,11 @@ cdef extern from "astrid.h":

cdef class MessageEvent:
    cdef lpmsg_t * msg
    cpdef int schedule(MessageEvent self, double now=*)
    cpdef int schedule(MessageEvent self, size_t now=*)

cdef class MidiEvent:
    cdef lpmsg_t * msg
    cpdef int schedule(MidiEvent self, double now)
    cpdef int schedule(MidiEvent self, size_t now)

cdef class ParamBucket:
    cdef object _params


@@ 315,7 345,7 @@ cdef class EventContext:
    cdef public int count
    cdef public int tick
    cdef public int vid
    cdef public double now
    cdef public size_t now
    cdef public double max_processing_time
    cdef public Instrument instrument


M pippi/renderer.pyx => pippi/renderer.pyx +70 -76
@@ 28,6 28,14 @@ from pippi.soundbuffer cimport SoundBuffer

NUM_COMRADES = 4

cdef dict DEFAULT_PARAMS = {
    'volume': 'float',
    'at-period': 'int', 
    'at-offset': 'int', 
    'at-reset': 'int', 
    'last-at': 'int', 
}

class InstrumentError(Exception):
    pass



@@ 56,12 64,6 @@ cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, int output_channels
    samplerate = int(buf.samplerate)
    length = <size_t>len(buf)

    if grid_interval > 0:
        msg.interval = grid_interval

    if grid_offset > 0:
        msg.offset = grid_offset

    # size of audio data 
    audiosize = length * channels * sizeof(lpfloat_t)
    headersize = (sizeof(size_t) * 3) + (sizeof(int) * 3) # 3 size_t, 3 int


@@ 139,7 141,7 @@ cdef class MessageEvent:
        strcpy(self.msg.msg, byte_params)
        strcpy(self.msg.instrument_name, byte_instrument_name)

    cpdef int schedule(MessageEvent self, double now=0):
    cpdef int schedule(MessageEvent self, size_t now=0):
        self.msg.initiated = now
        return send_play_message(self.msg[0])



@@ 173,7 175,7 @@ cdef class MidiEvent:
        strcpy(self.msg.instrument_name, byte_instrument_name)
        lpmidi_encode_msg(self.msg, channel, message_type, note, velocity)

    cpdef int schedule(MidiEvent self, double now):
    cpdef int schedule(MidiEvent self, size_t now):
        self.msg.initiated = now
        return send_play_message(self.msg[0])



@@ 374,7 376,7 @@ cdef class EventContext:
            size_t count=0,
        ):

        cdef double now = 0
        cdef size_t now = 0

        self.graph = graph
        self.cache = instrument.cache


@@ 385,16 387,23 @@ cdef class EventContext:
        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)
        if lpscheduler_get_now_ticks(&now, <int>instrument.samplerate) < 0:
            logger.exception('Error getting now ticks during %s event ctx init' % instrument.name)
            now = 0
        self.now = now

    @property
    def samplerate(self):
        return self.instrument.samplerate

    def adc(self, length=1, offset=0, channels=None):
        if channels is None:
            channels = self.instrument.input_channels
        return self.instrument.read_from_adc(length, offset=offset, channels=channels)

    def bpm2frames(self, double bpm):
        return 60./bpm * self.instrument.samplerate

    def load_sample(self, str name, double length=0, double offset=0, int channels=0):
        if length <= 0 and offset == 0:
            # just read the entire sample


@@ 452,7 461,8 @@ cdef class Instrument:
            double resampler_length, 
            str midi_input_device_name, 
            str midi_output_device_name,
            int is_interactive
            int is_interactive, 
            int autotrigger_is_enabled,
        ):
        cdef char * midi_input_device_cstr
        cdef char * midi_output_device_cstr


@@ 496,6 506,7 @@ cdef class Instrument:
        config.resampler_length = resampler_length
        config.ext_relay_enabled = 1
        config.is_interactive = is_interactive
        config.autotrigger_is_enabled = autotrigger_is_enabled

        self.i = astrid_instrument_start_from_config(config)
        if self.i == NULL:


@@ 548,17 559,30 @@ cdef class Instrument:
                else:
                    self.default_midi_device = 1

                self.instrument_param_type_map = {}
                self.instrument_param_type_map.update(DEFAULT_PARAMS)
                if hasattr(self.renderer, 'PARAMS'):
                    logger.debug('mapping params...')
                    self.instrument_param_type_map = self.renderer.PARAMS
                    self.instrument_param_hash_map = dict()
                    for k, t in self.instrument_param_type_map.items():
                        logger.debug('mapping %s...' % k)
                        k_byte_string = k.encode('UTF-8')
                        _k = k_byte_string
                        logger.debug('        %s...' % _k)
                        self.instrument_param_hash_map[k] = lphashstr(_k)
                        logger.debug('        %d...' % self.instrument_param_hash_map[k])
                    self.instrument_param_type_map.update(self.renderer.PARAMS)

                self.instrument_param_hash_map = dict()
                for k, t in self.instrument_param_type_map.items():
                    k_byte_string = k.encode('UTF-8')
                    _k = k_byte_string
                    self.instrument_param_hash_map[k] = lphashstr(_k)

                # Set the autotrigger environment if it's configured
                if hasattr(self.renderer, 'autotrigger'):
                    at = self.renderer.autotrigger
                    if callable(at):
                        at = at(ctx)
                    if isinstance(at, tuple):
                        if len(at) == 2:
                            grid_interval, grid_offset = at
                            setattr(ctx.s, 'at-period', grid_interval)
                            setattr(ctx.s, 'at-offset', grid_offset)
                    elif isinstance(at, numbers.Real):
                        grid_interval = at
                        setattr(ctx.s, 'at-period', grid_interval)

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


@@ 792,12 816,6 @@ cdef class Instrument:
        cdef EventContext ctx 
        cdef dict params
        cdef str p, k, v
        cdef size_t last_edit

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

        ctx = self.get_event_context()
        params = dict()


@@ 892,20 910,10 @@ cdef int render_event(Instrument instrument, str msgstr):
    if hasattr(instrument.renderer, 'before'):
        instrument.renderer.before(ctx)

    if hasattr(instrument.renderer, 'grid'):
        if isinstance(instrument.renderer.grid, tuple):
            grid_interval, grid_offset = instrument.renderer.grid
        elif isinstance(instrument.renderer.grid, numbers.Real):
            grid_interval = instrument.renderer.GRID
        elif callable(instrument.renderer.grid):
            grid_interval, grid_offset = instrument.renderer.grid(ctx)

    players, loop = collect_players(instrument)

    for player in players:
        try:
            ctx.count = 0
            ctx.tick = 0
            generator = player(ctx)

            try:


@@ 970,7 978,7 @@ cdef int trigger_events(Instrument instrument):
    cdef EventContext ctx 
    cdef list eventlist
    cdef int qfd
    cdef double now = 0
    cdef size_t now = 0
    cdef bytes trigger_params = instrument.msg.msg
    cdef list trigger_events = []



@@ 985,8 993,6 @@ cdef int trigger_events(Instrument instrument):
    planners = collect_trigger_planners(instrument)

    for p in planners:
        ctx.count = 0
        ctx.tick = 0
        try:
            eventlist = p(ctx)
            if eventlist is None:


@@ 999,8 1005,8 @@ cdef int trigger_events(Instrument instrument):
    # Schedule the trigger events
    logger.debug('Scheduling %d trigger events: %s' % (len(trigger_events), trigger_events))

    if lpscheduler_get_now_seconds(&now) < 0:
        logger.exception('Error getting now seconds during %s trigger scheduling' % ctx.instrument_name)
    if lpscheduler_get_now_ticks(&now, <int>instrument.samplerate) < 0:
        logger.exception('Error getting now ticks during %s trigger scheduling' % ctx.instrument_name)
        now = 0

    for t in trigger_events:


@@ 1030,15 1036,15 @@ def render_executor(Instrument instrument, object q, int comrade_id):
            logger.debug('Renderer comrade %d shutting down' % comrade_id)
            break

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

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

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

        if render_event(instrument, msg) < 0:
            logger.exception('Error trying to execute python render...')
            return


@@ 1051,13 1057,6 @@ def render_executor(Instrument instrument, object q, int comrade_id):
        logger.debug('%s render time: %f seconds' % (instrument.name, end - start))

cdef int astrid_schedule_python_triggers(Instrument instrument) except -1:
    cdef size_t last_edit = os.path.getmtime(instrument.path)

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

    try:
        return trigger_events(instrument)
    except Exception as e:


@@ 1070,11 1069,13 @@ def _run_forever(Instrument instrument,
        int input_channels, 
        int output_channels, 
        int is_interactive,
        int autotrigger_is_enabled,
        double initial_volume,
        double adc_length,
        double resampler_length,
        object q, 
    ):
    cdef size_t last_edit
    cdef lpmsg_t msg

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


@@ 1087,6 1088,15 @@ def _run_forever(Instrument instrument,
            print('There was a problem reading from the msg q. Maybe try turning it off and on again?')
            continue

        last_edit = os.path.getmtime(instrument.path)
        if last_edit > instrument.last_reload:
            try:
                instrument.reload()
                instrument.last_reload = last_edit
            except InstrumentError as e:
                logger.error('PY: Error trying to reload instrument. Shutting down...')
                break

        if msg.type == LPMSG_SHUTDOWN:
            logger.debug('PY MSG: shutdown')
            for _ in range(NUM_COMRADES):


@@ 1114,26 1124,7 @@ def _run_forever(Instrument instrument,
                logger.error('Error trying to schedule python triggers...')

        elif msg.type == LPMSG_LOAD:
            # FIXME pass this along to the comrades
            try:
                if instrument is None:
                    instrument = Instrument(instrument_name, 
                        script_path, 
                        input_channels, 
                        output_channels,
                        initial_volume, 
                        adc_length,
                        resampler_length,
                        is_interactive,
                    )
                else:
                    last_edit = os.path.getmtime(instrument.path)
                    if last_edit > instrument.last_reload:
                        instrument.reload()
                        instrument.last_reload = last_edit
            except InstrumentError as e:
                logger.error('PY: Error trying to reload instrument. Shutting down...')
                break
            pass

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



@@ 1149,6 1140,7 @@ def run_forever(
        str midi_input_device_name=None, 
        str midi_output_device_name=None,
        int is_interactive=1,
        int autotrigger_is_enabled=0,
    ):
    cdef Instrument instrument = None
    instrument_name = instrument_name if instrument_name is not None else Path(script_path).stem


@@ 1172,7 1164,8 @@ def run_forever(
            resampler_length, 
            midi_input_device_name, 
            midi_output_device_name,
            is_interactive
            is_interactive,
            autotrigger_is_enabled
        )
        logger.info(f'PY: started instrument... {script_path=} {instrument_name=}')
    except InstrumentError as e:


@@ 1193,6 1186,7 @@ def run_forever(
        input_channels, 
        output_channels, 
        is_interactive,
        autotrigger_is_enabled,
        initial_volume, 
        adc_length, 
        resampler_length, 


@@ 1209,7 1203,7 @@ def run_forever(
                time.sleep(2)
                continue

            for comrade in render_pool:
            for i, comrade in enumerate(render_pool):
                if not comrade.is_alive():
                    render_pool.remove(comrade)
                    logger.error('Our comrade has become exhausted, and is being relieved')