~hecanjog/pippi

21466de0eb76e1677faf6ff0e834d51758f01a34 — Erik Schoster a month ago d2d536b
First pass update message callback handling in python instruments. More littlefield instrument work. Misc bugfixes and improvements in serial listener, libpippi mir.yin, and astrid session param handling.
M astrid/Makefile => astrid/Makefile +1 -0
@@ 107,6 107,7 @@ littlefield:

	echo "Building littlefield...";

	#$(CC) $(LPFLAGS) -fsanitize=address -fsanitize=leak -fno-omit-frame-pointer $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/littlefield.c $(LPLIBS) -o build/littlefield
	$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/littlefield.c $(LPLIBS) -o build/littlefield
	sudo cp build/littlefield /usr/local/bin/


M astrid/orc/littlefield.c => astrid/orc/littlefield.c +102 -35
@@ 1,3 1,16 @@
/* TODO
 * + pitch: lowpass / smooth the tracked freq
 * + pitch: yin fallback pitch self-updates? (hold last stable)
 * + pitch: add param to toggle tracking on/off
 * + pitch: try only using tracked pitch for even oscs
 * + pitch: add terry ratios
 * + pitch: add LFO drift to freq
 * + pitch: add octave offset to freq
 * - pitch: add octave rand spread to freq
 * - mix: amp/gain param for in1 and in2
 * + oscs: remove distortion param
 */

#include "astrid.h"

#define NAME "littlefield"


@@ 13,7 26,20 @@

int num_freqs = MAXNUMFREQS;

lppatternbuf_t default_patternbuf = {1,{1}};
lpfloat_t terry[] = {
    1.f,         // P1  C
    16.f/15.f,   // m2  Db
    10.f/9.f,    // M2  D
    6.f/5.f,     // m3  Eb
    5.f/4.f,     // M3  E
    4.f/3.f,     // P4  F
    64.f/45.f,   // TT  Gb
    3.f/2.f,     // P5  G
    8.f/5.f,     // m6  Ab
    27.f/16.f,   // M6  A 
    16.f/9.f,    // m7  Bb
    15.f/8.f,    // M7  B
};

lpfloat_t scale[] = {
    55.000f, 


@@ 38,7 64,7 @@ enum InstrumentParams {
    PARAM_OSC_SATURATION,
    PARAM_OSC_ENVELOPE_SPEED,
    PARAM_OSC_DRIFT_DEPTH,
    PARAM_OSC_DISTORTION_AMOUNT,
    PARAM_OSC_DRIFT_SPEED,
    PARAM_OSC_OCTAVE_SPREAD,
    PARAM_OSC_OCTAVE_OFFSET,
    PARAM_OSC_FREQS,


@@ 52,20 78,25 @@ enum InstrumentParams {
    PARAM_GATE_PATTERN,
    PARAM_GATE_PHASE_RESET,
    PARAM_MIC_MIX,
    PARAM_MIC_PITCH_TRACKING_ENABLED,
    NUMPARAMS
};

typedef struct localctx_t {
    lppulsarosc_t * oscs[NUMOSCS];
    lppulsarosc_t * gate;
    lpyin_t * yin;
    lpfloat_t freqsmooth;
    lpfloat_t foldprev;
    lpfloat_t limitprev;
    lpbuffer_t * limitbuf;
    lpbuffer_t * env;
    lpbuffer_t * curves[NUMOSCS];
    lpbuffer_t * drifters[NUMOSCS];
    lpfloat_t drift_phaseincs[NUMOSCS];
    lpfloat_t env_phases[NUMOSCS];
    lpfloat_t env_phaseincs[NUMOSCS];
    lpfloat_t selected_freqs[MAXNUMFREQS];
    lpfloat_t octave_mults[NUMOSCS];
} localctx_t;

lpfloat_t osc_mix(lpfloat_t pos, int chan) {


@@ 93,7 124,7 @@ int param_map_callback(void * arg, char * keystr, char * valstr) {
    //localctx_t * ctx = (localctx_t *)instrument->context;
    lpfloat_t val_floatlist[MAXNUMFREQS] = {220.};
    float val_f = 0;
    uint32_t val_i32 = 0;
    int32_t val_i32 = 0;
    lppatternbuf_t val_pattern = {1,{1}};

    syslog(LOG_DEBUG, "Got param %s=%s\n", keystr, valstr);


@@ 116,9 147,9 @@ int param_map_callback(void * arg, char * keystr, char * valstr) {
    } else if(strcmp(keystr, "odrift") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_DRIFT_DEPTH, val_f);
    } else if(strcmp(keystr, "odist") == 0) {
    } else if(strcmp(keystr, "odspeed") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_DISTORTION_AMOUNT, val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_DRIFT_SPEED, val_f);
    } else if(strcmp(keystr, "octspread") == 0) {
        extract_int32_from_token(valstr, &val_i32);
        astrid_instrument_set_param_int32(instrument, PARAM_OSC_OCTAVE_SPREAD, val_i32);


@@ 157,6 188,9 @@ int param_map_callback(void * arg, char * keystr, char * valstr) {
    } else if(strcmp(keystr, "mmix") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_MIC_MIX, val_f);
    } else if(strcmp(keystr, "mtrak") == 0) {
        extract_int32_from_token(valstr, &val_i32);
        astrid_instrument_set_param_int32(instrument, PARAM_MIC_PITCH_TRACKING_ENABLED, val_i32);
    }    

    return 0;


@@ 183,39 217,41 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg
    size_t i;
    int j;
    lpfloat_t freqs[MAXNUMFREQS];
    lpfloat_t sample, left, right, amp, env, in0, in1;
    lpfloat_t sample, left, right, osc_amp, env, in0, in1, p, last_p=-1;
    lpfloat_t osc_pw, osc_saturation, osc_env_speed, 
              osc_distortion, osc_mix_ctl, osc_drift, 
              mic_mix_ctl;
    uint32_t octave_spread, octave_offset, gate_reset;
              osc_mix_ctl, osc_drift_amount, drift, osc_drift_speed,
              gated_sample, gate_amp, octave_mult,
              mic_mix_ctl, tracked_freq=220.f;
    int32_t octave_spread, octave_offset, gate_reset, pitch_tracking_is_enabled;
    lpfloat_t gate_shape, gate_speed, gate_pw, 
              gate_saturation;
    lppatternbuf_t gate_pattern = {1,{1}};
    lppatternbuf_t gate_pattern;
    lpinstrument_t * instrument = (lpinstrument_t *)arg;
    localctx_t * ctx = (localctx_t *)instrument->context;

    if(!instrument->is_running) return 1;

    amp = astrid_instrument_get_param_float(instrument, PARAM_OSC_AMP, 0.5f);
    osc_amp = astrid_instrument_get_param_float(instrument, PARAM_OSC_AMP, 0.5f);
    osc_mix_ctl = astrid_instrument_get_param_float(instrument, PARAM_OSC_MIX, 0.5f);
    osc_pw = astrid_instrument_get_param_float(instrument, PARAM_OSC_PULSEWIDTH, 1.f);
    osc_saturation = astrid_instrument_get_param_float(instrument, PARAM_OSC_SATURATION, 1.f);
    osc_env_speed = astrid_instrument_get_param_float(instrument, PARAM_OSC_ENVELOPE_SPEED, 0.5f) * 1000.f + 1.f;
    osc_drift = astrid_instrument_get_param_float(instrument, PARAM_OSC_DRIFT_DEPTH, 0.f);
    osc_distortion = astrid_instrument_get_param_float(instrument, PARAM_OSC_DISTORTION_AMOUNT, 0.f);
    osc_env_speed = astrid_instrument_get_param_float(instrument, PARAM_OSC_ENVELOPE_SPEED, 0.001f) * 1000.f + 1.f;
    osc_drift_amount = astrid_instrument_get_param_float(instrument, PARAM_OSC_DRIFT_DEPTH, 0.f) * 10.f;
    osc_drift_speed = astrid_instrument_get_param_float(instrument, PARAM_OSC_DRIFT_SPEED, 0.5f);
    octave_spread = astrid_instrument_get_param_int32(instrument, PARAM_OSC_OCTAVE_SPREAD, 0);
    octave_offset = astrid_instrument_get_param_int32(instrument, PARAM_OSC_OCTAVE_OFFSET, 2);
    octave_offset = astrid_instrument_get_param_int32(instrument, PARAM_OSC_OCTAVE_OFFSET, 0);
    astrid_instrument_get_param_float_list(instrument, PARAM_OSC_FREQS, num_freqs, freqs);

    gate_amp = astrid_instrument_get_param_float(instrument, PARAM_GATE_AMP, 0.f);
    gate_reset = astrid_instrument_get_param_int32(instrument, PARAM_GATE_PHASE_RESET, 0);
    gate_shape = astrid_instrument_get_param_float(instrument, PARAM_GATE_SHAPE, 0.f);
    gate_speed = astrid_instrument_get_param_float(instrument, PARAM_GATE_SPEED, 1.f);
    gate_saturation = astrid_instrument_get_param_float(instrument, PARAM_GATE_SATURATION, 1.f);
    gate_pw = astrid_instrument_get_param_float(instrument, PARAM_GATE_PULSEWIDTH, 1.f);
    gate_pattern = astrid_instrument_get_param_patternbuf(instrument, PARAM_GATE_PATTERN, default_patternbuf);
    gate_pattern = astrid_instrument_get_param_patternbuf(instrument, PARAM_GATE_PATTERN);

    mic_mix_ctl = astrid_instrument_get_param_float(instrument, PARAM_MIC_MIX, 0.25f);

    pitch_tracking_is_enabled = astrid_instrument_get_param_int32(instrument, PARAM_MIC_PITCH_TRACKING_ENABLED, 0);

    ctx->gate->window_morph = fmin(gate_shape, 0.999f); // TODO investigate overflows
    ctx->gate->freq = gate_speed;


@@ 230,36 266,65 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg

    for(i=0; i < blocksize; i++) {
        sample = 0.f;

        p = LPPitchTracker.yin_process(ctx->yin, input[0][i]);
        if(p > 0 && p != last_p) {
            tracked_freq = p;
            last_p = p;
        }

        tracked_freq = LPFX.lpf1(tracked_freq, &ctx->freqsmooth, 20.f, SR);

        // track in a ~melodic range
        while(tracked_freq >= 800) tracked_freq *= 0.5f;

        for(j=0; j < NUMOSCS; j++) {
            // TODO use this LFO for the freq drift -- use phase offsets to vary the drift amount
            //saturation = LPInterpolation.linear_pos(ctx->curves[j], ctx->curves[j]->phase) * 0.05f + 0.95f;
            ctx->oscs[j]->saturation = osc_saturation;
            ctx->oscs[j]->pulsewidth = osc_pw;
            // TODO apply the octave spread here, use the drift LFO to vary the spread
            ctx->oscs[j]->freq = freqs[j % num_freqs] * (osc_drift + 1.f);

            //drift_mult = LPInterpolation.linear_pos(ctx->curves[j], ctx->curves[j]->phase) * osc_drift_amount;
            drift = LPInterpolation.linear_pos(ctx->drifters[j], ctx->drifters[j]->phase) * osc_drift_amount;

            if(pitch_tracking_is_enabled && (j % 2 == 0)) {
                ctx->oscs[j]->freq = tracked_freq * ctx->octave_mults[j] + drift;
            } else {
                ctx->oscs[j]->freq = freqs[j % num_freqs] * ctx->octave_mults[j] + drift;
            }

            sample += LPPulsarOsc.process(ctx->oscs[j]) * LPInterpolation.linear_pos(ctx->env, ctx->env_phases[j]) * ((1.f/NUMOSCS)*3.f);

            ctx->env_phases[j] += ctx->env_phaseincs[j] * (1.f+osc_env_speed) * 3.f;

            if(ctx->env_phases[j] >= 1.f) {
                // something special on phase resets maybe?
                syslog(LOG_DEBUG, "!!!! OCTAVE OFFSET: %d\n", octave_offset);
                octave_mult = pow(2, octave_offset);
                syslog(LOG_DEBUG, "!!!! OCTAVE MULT: %f\n", octave_mult);
                syslog(LOG_DEBUG, "!!!! OCTAVE SPREAD: %d\n", octave_spread);
                octave_spread = LPRand.randint(-octave_spread, octave_spread);
                syslog(LOG_DEBUG, ">>>> OCTAVE SPREAD: %d\n", octave_spread);
                octave_mult *= pow(2, octave_spread);
                syslog(LOG_DEBUG, ">>>> OCTAVE MULT: %f\n", octave_mult);
                ctx->octave_mults[j] = octave_mult;
            }

            while(ctx->env_phases[j] >= 1.f) ctx->env_phases[j] -= 1.f;

            ctx->curves[j]->phase += ctx->env_phaseincs[j];
            while(ctx->curves[j]->phase >= 1.f) ctx->curves[j]->phase -= 1.f;

            ctx->drifters[j]->phase += ctx->drift_phaseincs[j] * osc_drift_speed;
            while(ctx->drifters[j]->phase >= 1.f) ctx->drifters[j]->phase -= 1.f;
        }

        sample *= amp;
        env = LPPulsarOsc.process(ctx->gate);
        gated_sample = sample * env * gate_amp;

        sample *= osc_amp;
        sample = sample + gated_sample;
        sample = LPFX.limit(sample, &ctx->limitprev, 1.f, 0.2f, ctx->limitbuf);
        if(sample > 1.f || sample < -1.f) {
            sample = LPFX.fold(sample, &ctx->foldprev, instrument->samplerate);
        }

        sample = LPFX.limit(sample, &ctx->limitprev, 1.f, 0.2f, ctx->limitbuf);

        left = right = sample;

        left *= osc_mix(osc_mix_ctl, 0);


@@ 268,9 333,8 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg
        in0 = input[0][i] * mic_mix(mic_mix_ctl, 0) * MIC_ATTENUATION;
        in1 = input[1][i] * mic_mix(mic_mix_ctl, 1) * MIC_ATTENUATION;

        env = LPPulsarOsc.process(ctx->gate);
        output[0][i] += (left + in0) * env;
        output[1][i] += (right + in1) * env;
        output[0][i] += left + in0;
        output[1][i] += right + in1;
    }

    return 0;


@@ 297,6 361,9 @@ int main() {
        ctx->env_phases[i] = LPRand.rand(0.f, 1.f);
        ctx->env_phaseincs[i] = (1.f/SR) * LPRand.rand(0.005f, 0.03f) * 0.5f;
        ctx->curves[i] = LPWindow.create(WIN_RND, 4096);
        ctx->drifters[i] = LPWavetable.create(WT_RND, 4096);
        ctx->drift_phaseincs[i] = (1.f/SR) * LPRand.rand(0.005f, 0.03f);
        ctx->octave_mults[i] = 1.f;

        ctx->oscs[i] = LPPulsarOsc.create(4, 2,
            WT_SINE, WTSIZE, WT_TRI2, WTSIZE, 


@@ 316,6 383,9 @@ int main() {
        WIN_SINEOUT, WTSIZE
    );

    ctx->yin = LPPitchTracker.yin_create(4096, SR);
    ctx->yin->fallback = 220.f;

    ctx->gate->samplerate = (lpfloat_t)SR;
    ctx->gate->window_morph_freq = 0;
    ctx->gate->freq = 30.f;


@@ 323,16 393,13 @@ int main() {

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

    /* now that LMDB is running, populate the initial freqs */
    for(int i=0; i < MAXNUMFREQS; i++) {
        ctx->selected_freqs[i] = scale[LPRand.randint(0, MAXNUMFREQS*2) % MAXNUMFREQS] * 0.5f + LPRand.rand(0.f, 1.f);
    }
    astrid_instrument_set_param_float_list(instrument, PARAM_OSC_FREQS, ctx->selected_freqs, MAXNUMFREQS);
    astrid_instrument_set_param_float_list(instrument, PARAM_OSC_FREQS, scale, 12);

    /* twiddle thumbs until shutdown */
    while(instrument->is_running) {

M astrid/orc/simple.py => astrid/orc/simple.py +10 -42
@@ 1,52 1,20 @@
from pippi import dsp, oscs, renderer, fx, shapes, tune
from pippi import dsp, oscs, renderer

def trigger(ctx):
    return None
    #onset = dsp.rand(0.01, dsp.rand(0.4, dsp.rand(0.8, 1)))
    #onset = dsp.choice([0.1, 0.2, 0.3]) * 2.25
    onset = 3
    #return None
    events = []

    pos = 0
    while pos < onset:
        events += [ ctx.t.play(pos, 'simple') ]
        events += [ ctx.t.serial(pos+0.05, 'simple', 's %d' % dsp.randint(0, 4)) ]
        pos += dsp.rand(0.01, 0.14)
    events += [ ctx.t.trigger(onset, 'simple') ]
    events += [ ctx.t.play(0, 'simple') ]
    events += [ ctx.t.update(0, 'littlefield', 'gamp=%s' % dsp.rand(0,1)) ]
    events += [ ctx.t.trigger(2, 'simple') ]
    return events

def update(ctx, k, v):
    ctx.log('k: %s v: %s' % (k, v))
    ctx.t.update(0, 'littlefield', 'gamp=%s' % dsp.rand(0,1)).schedule()

def play(ctx):
    ctx.log('Rendering simple tone!')
    length = dsp.rand(0.01, dsp.rand(0.5, dsp.rand(1.4, 2)))
    #length = 2
    pw = shapes.win('sine', dsp.rand(0, 0.8), 1)

    """
    adcpass = ctx.adc(length)
    sbase = dsp.rand(0.1, 0.8)
    adc = adcpass.vspeed(shapes.win('sine', sbase, sbase+0.2)).rcut(length)
    #adc = fx.fold(adc, dsp.rand(2, 5))
    fbase = dsp.rand(100, 10000)
    adc = fx.bpf(adc, shapes.win('sine', fbase, fbase + 100))
    adc = fx.norm(adc, 1).pan(shapes.win('sine'))

    wts = dsp.ws(adc.rcut(0.2))
    wts.normalize()
    """
    wts = ['rnd'] * 10

    freqs = tune.degrees([ dsp.choice([1,3,5,6,8]) for _ in range(dsp.randint(3,6)) ], octave=4, key='d')
    #freqs = [22]

    out = dsp.buffer(length=length)
    for freq in freqs:
        o = oscs.Pulsar2d(wts, pulsewidth=pw, freq=freq * 2**dsp.randint(0,3), amp=dsp.rand(0.2, 0.5)).play(length)
        out.dub(o)
    #out = out & out.env(adc)
    #out = fx.bpf(out, shapes.win('sine', 100, 10000)) & (adcpass.env('hann') * 0.1)

    #yield fx.norm(out, dsp.rand(0.75, 0.85)).env(shapes.win('rnd', dsp.rand(0, 0.8), 1)).env('pluckout')
    yield fx.norm(out, dsp.rand(0.75, 0.85)).env('pluckout')
    yield oscs.SineOsc(freq=530, amp=0.5).play(1).env('pluckout')

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

M astrid/src/astrid.c => astrid/src/astrid.c +28 -21
@@ 1727,7 1727,7 @@ int astrid_get_capture_device_id() {
    return astrid_get_playback_device_id();
}

int extract_int32_from_token(char * token, uint32_t * val) {
int extract_int32_from_token(char * token, int32_t * val) {
    char * end;
    long result = 0;



@@ 1739,7 1739,7 @@ int extract_int32_from_token(char * token, uint32_t * val) {

    if(result > INT_MAX) result = INT_MAX;

    *val = (uint32_t)result;
    *val = (int32_t)result;
    return 0;
}



@@ 1813,8 1813,8 @@ int encode_update_message_param(lpmsg_t * msg) {
    char *token, *save=NULL;
    char param_copy[LPMAXMSG] = {0};

    uint32_t vali32 = 0;
    uint16_t vali16 = 0;
    int32_t vali32 = 0;
    int16_t vali16 = 0;
    float valf = 0.f;

    memcpy(param_copy, msg->msg, LPMAXMSG);


@@ 1854,10 1854,10 @@ int encode_update_message_param_float(uint16_t id, char * valstr, lpmsg_t * msg)
}

int encode_update_message_param_int32(uint16_t id, char * valstr, lpmsg_t * msg) {
    uint32_t val = 0;
    uint16_t val16 = 0;
    int32_t val = 0;
    int16_t val16 = 0;
    if(extract_int32_from_token(valstr, &val) < 0) {
        syslog(LOG_ERR, "encode_update_message_param_int32: Could not extract uint32_t from token '%s'\n", valstr);
        syslog(LOG_ERR, "encode_update_message_param_int32: Could not extract int32_t from token '%s'\n", valstr);
        return -1;
    }



@@ 1880,11 1880,6 @@ int process_param_updates(lpinstrument_t * instrument) {
    char * cmdline_save;
    char * paramline_save;

    if(instrument->cmd.type != LPMSG_UPDATE) {
        syslog(LOG_WARNING, "process_param_updates: instrument->cmd.type is '%d' skipping...\n", instrument->cmd.type);
        return 0;
    }

    memcpy(cmdline, instrument->msg.msg, LPMAXMSG);

    paramline = strtok_r(cmdline, " ", &cmdline_save);


@@ 3087,9 3082,14 @@ void * instrument_serial_listener_thread(void * arg) {
    size_t bytes_in_buf=0;
    lpinstrument_t * instrument = (lpinstrument_t *)arg;

    if(!instrument->tty_is_enabled) {
        syslog(LOG_INFO, "%s serial listener: no tty requested, shutting down here!\n", instrument->name);
        return NULL;
    }

    // Open the tty 
    // TODO support multiple ttys, duuuh
    tty = open("/dev/ttyACM0", O_RDWR | O_NOCTTY);
    tty = open(instrument->tty_path, O_RDWR | O_NOCTTY);
    if(tty < 0) {
        syslog(LOG_ERR, "Problem connecting to TTY -- shutting down serial listener thread...\n");
        return NULL;


@@ 3237,6 3237,7 @@ lpinstrument_t * astrid_instrument_start(
    int ext_relay_enabled,
    double adc_length,
    void * ctx,
    char * tty,
    int (*stream)(size_t blocksize, float ** input, float ** output, void * instrument),
    int (*renderer)(void * instrument),
    int (*update)(void * instrument, char * key, char * val),


@@ 3291,6 3292,12 @@ lpinstrument_t * astrid_instrument_start(
    snprintf(instrument->qname, NAME_MAX, "/%s-msgq", instrument->name);
    snprintf(instrument->serial_message_q_name, NAME_MAX, "/%s-serial-msgq", instrument->name);

    // Set the tty path
    if(tty != NULL) {
        snprintf(instrument->tty_path, NAME_MAX, "%s", tty);
        instrument->tty_is_enabled = 1;
    }

    if(instrument->ext_relay_enabled) {
        snprintf(instrument->external_relay_name, NAME_MAX, "/%s-extrelay-msgq", instrument->name);
    } 


@@ 3788,32 3795,32 @@ int astrid_instrument_tick(lpinstrument_t * instrument) {
    return 0;
}

uint32_t astrid_instrument_get_param_int32(lpinstrument_t * instrument, int param_index, uint32_t default_value) {
int32_t astrid_instrument_get_param_int32(lpinstrument_t * instrument, int param_index, int32_t default_value) {
    int rc;
    MDB_val key, data;
    uint32_t param = default_value;
    int32_t param = default_value;

    key.mv_size = sizeof(int);
    key.mv_data = (void *)(&param_index);
    data.mv_size = sizeof(uint32_t);
    data.mv_size = sizeof(int32_t);

	rc = mdb_txn_renew(instrument->dbtxn_read);
    rc = mdb_get(instrument->dbtxn_read, instrument->dbi, &key, &data);
    if(rc == 0) {
        param = *((uint32_t *)data.mv_data);
        param = *((int32_t *)data.mv_data);
    }
    mdb_txn_reset(instrument->dbtxn_read);

    return param;
}

void astrid_instrument_set_param_int32(lpinstrument_t * instrument, int param_index, uint32_t value) {
void astrid_instrument_set_param_int32(lpinstrument_t * instrument, int param_index, int32_t value) {
    int rc;
	MDB_val key, data;

    key.mv_size = sizeof(int);
    key.mv_data = (void *)(&param_index);
    data.mv_size = sizeof(uint32_t);
    data.mv_size = sizeof(int32_t);
    data.mv_data = (void *)(&value);

	rc = mdb_txn_begin(instrument->dbenv, NULL, 0, &instrument->dbtxn_write);


@@ 3879,10 3886,10 @@ void astrid_instrument_set_param_patternbuf(lpinstrument_t * instrument, int par
    }
}

lppatternbuf_t astrid_instrument_get_param_patternbuf(lpinstrument_t * instrument, int param_index, lppatternbuf_t default_patternbuf) {
lppatternbuf_t astrid_instrument_get_param_patternbuf(lpinstrument_t * instrument, int param_index) {
    int rc;
    MDB_val key, data;
    lppatternbuf_t patternbuf = default_patternbuf;
    lppatternbuf_t patternbuf = {1,{1}};

    key.mv_size = sizeof(int);
    key.mv_data = (void *)(&param_index);

M astrid/src/astrid.h => astrid/src/astrid.h +8 -5
@@ 168,6 168,9 @@ typedef struct lpinstrument_t {
    lpmsg_t msg;
    lpmsg_t cmd;

    int tty_is_enabled;
    char tty_path[NAME_MAX]; 

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


@@ 317,13 320,13 @@ int lpipc_destroyvalue(char * id_path);

void lptimeit_since(struct timespec * start);

lpinstrument_t * astrid_instrument_start(char * name, int channels, int ext_relay_enabled, double adc_length, void * ctx, int (*stream)(size_t blocksize, float ** input, float ** output, void * instrument), int (*renderer)(void * instrument), int (*update)(void * instrument, char * key, char * val), int (*trigger)(void * instrument));
lpinstrument_t * astrid_instrument_start(char * name, int channels, int ext_relay_enabled, double adc_length, void * ctx, char * tty, int (*stream)(size_t blocksize, float ** input, float ** output, void * instrument), int (*renderer)(void * instrument), int (*update)(void * instrument, char * key, char * val), int (*trigger)(void * instrument));
int astrid_instrument_stop(lpinstrument_t * instrument);

uint32_t astrid_instrument_get_param_int32(lpinstrument_t * instrument, int param_index, uint32_t default_value);
void astrid_instrument_set_param_int32(lpinstrument_t * instrument, int param_index, uint32_t value);
int32_t astrid_instrument_get_param_int32(lpinstrument_t * instrument, int param_index, int32_t default_value);
void astrid_instrument_set_param_int32(lpinstrument_t * instrument, int param_index, int32_t value);
void astrid_instrument_set_param_patternbuf(lpinstrument_t * instrument, int param_index, lppatternbuf_t * patternbuf);
lppatternbuf_t astrid_instrument_get_param_patternbuf(lpinstrument_t * instrument, int param_index, lppatternbuf_t default_patternbuf);
lppatternbuf_t astrid_instrument_get_param_patternbuf(lpinstrument_t * instrument, int param_index);
void astrid_instrument_set_param_float(lpinstrument_t * instrument, int param_index, lpfloat_t value);
lpfloat_t astrid_instrument_get_param_float(lpinstrument_t * instrument, int param_index, lpfloat_t default_value);
void astrid_instrument_set_param_float_list(lpinstrument_t * instrument, int param_index, lpfloat_t * value, size_t size);


@@ 337,7 340,7 @@ int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * buf
int send_render_to_mixer(lpinstrument_t * instrument, lpbuffer_t * buf);
int relay_message_to_seq(lpinstrument_t * instrument);

int extract_int32_from_token(char * token, uint32_t * val);
int extract_int32_from_token(char * token, int32_t * val);
int extract_float_from_token(char * token, float * val);
int extract_floatlist_from_token(char * tokenlist, lpfloat_t * val, int size);
int extract_patternbuf_from_token(char * token, unsigned char * patternbuf, size_t * pattern_length);

M libpippi/src/mir.c => libpippi/src/mir.c +1 -0
@@ 67,6 67,7 @@ lpfloat_t yin_process(lpyin_t * yin, lpfloat_t sample) {
        yin_difference_function(yin);
        yin_cumulative_mean_normalized_difference_function(yin);
        p = yin_get_pitch(yin);
        yin->last_pitch = p;
        yin->elapsed = 0;
    } else {
        p = yin->last_pitch;

M libpippi/src/oscs.pulsar.c => libpippi/src/oscs.pulsar.c +4 -3
@@ 43,17 43,18 @@ void burst_table_from_bytes(lppulsarosc_t * osc, unsigned char * bytes, size_t b
    int mask;
    size_t i, c, pos;
    bool * burst_table;
    size_t num_bytes = burst_size / sizeof(unsigned char) + 1;
    size_t num_bytes = (burst_size + sizeof(unsigned char) - 1) / sizeof(unsigned char);

    burst_table = (bool *)LPMemoryPool.alloc(burst_size, sizeof(bool));
    if(burst_table == NULL) return;

    pos = 0;
    for(i=0; i < num_bytes; i++) {
        for(c=0; c < sizeof(unsigned char); c++) {
            if(pos >= burst_size) break;
            mask = 1 << c;
            burst_table[pos] = (bool)((bytes[i] & mask) >> c);
            pos += 1;
            if(pos >= burst_size) break;
        }
    }



@@ 63,7 64,7 @@ void burst_table_from_bytes(lppulsarosc_t * osc, unsigned char * bytes, size_t b

void burst_table_from_file(lppulsarosc_t * osc, char * filename, size_t burst_size) {
    int fp;
    size_t num_bytes = burst_size / sizeof(unsigned char) + 1;
    size_t num_bytes = (burst_size + sizeof(unsigned char) - 1) / sizeof(unsigned char);
    unsigned char burst_buffer[num_bytes];

    memset(burst_buffer, 0, num_bytes * sizeof(unsigned char));

M pippi/renderer.pxd => pippi/renderer.pxd +2 -1
@@ 151,6 151,7 @@ cdef extern from "astrid.h":
        int ext_relay_enabled,
        double adc_length,
        void * ctx, 
        char * tty,
        int (*stream)(size_t blocksize, float ** input, float ** output, void * instrument),
        int (*renderer)(void * instrument),
        int (*update)(void * instrument),


@@ 167,7 168,7 @@ cdef extern from "astrid.h":

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

cdef class MidiEvent:
    cdef lpmidievent_t * event

M pippi/renderer.pyx => pippi/renderer.pyx +39 -30
@@ 106,7 106,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):
    cpdef int schedule(MessageEvent self, double now=0):
        self.msg.initiated = now
        return send_play_message(self.msg[0])



@@ 337,7 337,7 @@ cdef class Instrument:
        self.last_reload = 0
        self.max_processing_time = 0

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



@@ 372,6 372,10 @@ cdef class Instrument:
                if hasattr(self.renderer, 'cache'):
                    self.cache = self.renderer.cache()

                if hasattr(self.renderer, 'stream'):
                    ctx = self.get_event_context()
                    self.graph = self.renderer.stream(ctx)

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


@@ 387,8 391,6 @@ cdef class Instrument:

        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


@@ 453,7 455,6 @@ cdef class Instrument:
                renderer._ = None

            self.renderer = renderer
            self.register_midi_triggers()
        else:
            logger.error('Error reloading instrument. Null spec at path:\n  %s' % self.path)



@@ 461,31 462,38 @@ cdef class Instrument:
        if hasattr(self.renderer, 'cache'):
            self.cache = self.renderer.cache()

    def register_midi_triggers(self):
        # FIXME prolly don't need this anymore?
        if hasattr(self.renderer, 'MIDI'): 
            devices = []
            if isinstance(self.renderer.MIDI, list):
                devices += self.renderer.MIDI
            else:
                devices = [ self.renderer.MIDI ]
        if hasattr(self.renderer, 'stream'):
            ctx = self.get_event_context()
            self.graph = self.renderer.stream(ctx)


cdef int stream_graph_update(object instrument):
    cdef EventContext ctx 
    def handle_update_message(self, str msgstr):
        cdef EventContext ctx 
        cdef dict params
        cdef str p, k, v

    if not hasattr(instrument.renderer, 'stream'):
        return 0
        if not hasattr(self.renderer, 'update'):
            logger.warning('Ignoring update message: this instrument has no callback registered')
            return None

    ctx = instrument.get_event_context()
    try:
        # 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
        ctx = self.get_event_context()
        params = dict()

        try:
            # read msg body, split and call update with each
            for p in msgstr.split(' '):
                if '=' in p:
                    p = p.strip()
                    k, v = tuple(p.split('='))
                else:
                    v = None
                    k = p.strip()
                    if k == '':
                        continue
                self.renderer.update(ctx, k.strip(), v.strip())
        except Exception as e:
            logger.exception('Error during %s update message handling: %s' % (self.name, e))

    return 0

cdef tuple collect_players(Instrument instrument):
    loop = False


@@ 701,16 709,18 @@ def _run_forever(Instrument instrument,
    ):
    cdef lpmsg_t msg

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

    while True:
        logger.info('waiting for a message...')
        logger.info('PY MSG: waiting for a message...')
        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

        logger.info('PY MSG: got one!')

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


@@ 719,8 729,7 @@ def _run_forever(Instrument instrument,

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

        elif msg.type == LPMSG_PLAY:
            logger.info('PY MSG: play')


@@ 745,7 754,7 @@ def _run_forever(Instrument instrument,
                logger.error('Error trying to reload instrument. Shutting down...')
                break

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

def run_forever(str script_path, str instrument_name=None, int channels=2, double adc_length=30):
    cdef Instrument instrument = None