~hecanjog/pippi

d9d0c43da527eca727f2f02bb18581d2072669d2 — Erik Schoster 7 days ago 21466de
Another astrid MIDI implementation, this time with the ALSA C API. Added pulsar osc one-shot behavior, and misc updates to littlefield instrument.
M astrid/Makefile => astrid/Makefile +1 -1
@@ 27,7 27,7 @@ LPDBSOURCES = $(LPDIR)/vendor/sqlite3/sqlite3.c
LPINCLUDES = -Isrc -I${LPDIR}../pippi -I$(LPDIR)/vendor -I$(LPDIR)/src -I${LPDIR}/vendor/linenoise -I${LPDIR}/vendor/libpqueue/src -I${LPDIR}/vendor/lmdb/libraries/liblmdb
LPDBINCLUDES = -I${LPDIR}/vendor/sqlite3
LPFLAGS = -g -std=gnu2x -Wall -Wextra -pedantic -O0 -DNOPYTHON
LPLIBS = -lm -ldl -lpthread -lrt -ljack
LPLIBS = -lm -ldl -lpthread -lrt -ljack -lasound

default: littlefield


M astrid/orc/littlefield.c => astrid/orc/littlefield.c +162 -64
@@ 1,16 1,3 @@
/* 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"


@@ 59,7 46,6 @@ lpfloat_t scale[] = {
enum InstrumentParams {
    PARAM_NOOP,
    PARAM_OSC_AMP,
    PARAM_OSC_MIX,
    PARAM_OSC_PULSEWIDTH,
    PARAM_OSC_SATURATION,
    PARAM_OSC_ENVELOPE_SPEED,


@@ 68,32 54,58 @@ enum InstrumentParams {
    PARAM_OSC_OCTAVE_SPREAD,
    PARAM_OSC_OCTAVE_OFFSET,
    PARAM_OSC_FREQS,

    PARAM_GATE_AMP,
    PARAM_GATE_MIX,
    PARAM_GATE_SPEED,
    PARAM_GATE_PULSEWIDTH,
    PARAM_GATE_SATURATION,
    PARAM_GATE_SHAPE,
    PARAM_GATE_DRIFT_DEPTH,
    PARAM_GATE_DRIFT_SPEED,
    PARAM_GATE_PATTERN,
    PARAM_GATE_PHASE_RESET,
    PARAM_MIC_MIX,
    PARAM_GATE_REPEAT,

    PARAM_IN1_TO_OUT1,
    PARAM_IN1_TO_OUT2,
    PARAM_IN2_TO_OUT1,
    PARAM_IN2_TO_OUT2,

    PARAM_OSC_TO_OUT1,
    PARAM_OSC_TO_OUT2,
    PARAM_GATE_TO_OUT1,
    PARAM_GATE_TO_OUT2,

    PARAM_DISTORTION_TYPE,
    PARAM_DISTORTION_AMOUNT,
    PARAM_DISTORTION_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;

    lpfloat_t fold1prev;
    lpfloat_t fold2prev;

    lpfloat_t limit1prev;
    lpfloat_t limit2prev;
    lpbuffer_t * limit1buf;
    lpbuffer_t * limit2buf;

    lppulsarosc_t * gate;
    lpshapeosc_t * gate_drifter;
    lpbuffer_t * gate_drift_win;

    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 octave_mults[NUMOSCS];


@@ 121,7 133,6 @@ lpfloat_t mic_mix(lpfloat_t pos, int chan) {

int param_map_callback(void * arg, char * keystr, char * valstr) {
    lpinstrument_t * instrument = (lpinstrument_t *)arg;
    //localctx_t * ctx = (localctx_t *)instrument->context;
    lpfloat_t val_floatlist[MAXNUMFREQS] = {220.};
    float val_f = 0;
    int32_t val_i32 = 0;


@@ 132,9 143,6 @@ int param_map_callback(void * arg, char * keystr, char * valstr) {
    if(strcmp(keystr, "oamp") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_AMP, val_f);
    } else if(strcmp(keystr, "omix") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_MIX, val_f);
    } else if(strcmp(keystr, "opw") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_PULSEWIDTH, val_f);


@@ 159,12 167,10 @@ int param_map_callback(void * arg, char * keystr, char * valstr) {
    } else if(strcmp(keystr, "freqs") == 0) {
        num_freqs = extract_floatlist_from_token(valstr, val_floatlist, MAXNUMFREQS);
        astrid_instrument_set_param_float_list(instrument, PARAM_OSC_FREQS, val_floatlist, num_freqs);

    } else if(strcmp(keystr, "gamp") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_AMP, val_f);
    } else if(strcmp(keystr, "gmix") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_MIX, val_f);
    } else if(strcmp(keystr, "gspeed") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_SPEED, val_f);


@@ 180,14 186,55 @@ int param_map_callback(void * arg, char * keystr, char * valstr) {
    } else if(strcmp(keystr, "gdrift") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_DRIFT_DEPTH, val_f);
   } else if(strcmp(keystr, "gdspeed") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_DRIFT_SPEED, val_f);
    } else if(strcmp(keystr, "gpat") == 0) {
        extract_patternbuf_from_token(valstr, val_pattern.pattern, &val_pattern.length);
        astrid_instrument_set_param_patternbuf(instrument, PARAM_GATE_PATTERN, &val_pattern);
    } else if(strcmp(keystr, "greset") == 0) {
        astrid_instrument_set_param_int32(instrument, PARAM_GATE_PHASE_RESET, 1);
    } else if(strcmp(keystr, "mmix") == 0) {
    } else if(strcmp(keystr, "grep") == 0) {
        extract_int32_from_token(valstr, &val_i32);
        astrid_instrument_set_param_int32(instrument, PARAM_GATE_REPEAT, val_i32);

    } else if(strcmp(keystr, "i1o1") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_IN1_TO_OUT1, val_f);
    } else if(strcmp(keystr, "i1o2") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_IN1_TO_OUT2, val_f);
    } else if(strcmp(keystr, "i2o1") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_MIC_MIX, val_f);
        astrid_instrument_set_param_float(instrument, PARAM_IN2_TO_OUT1, val_f);
    } else if(strcmp(keystr, "i2o2") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_IN2_TO_OUT2, val_f);

    } else if(strcmp(keystr, "oo1") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_TO_OUT1, val_f);
    } else if(strcmp(keystr, "oo2") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_OSC_TO_OUT2, val_f);

    } else if(strcmp(keystr, "go1") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_TO_OUT1, val_f);
    } else if(strcmp(keystr, "go2") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_GATE_TO_OUT2, val_f);

    } else if(strcmp(keystr, "disttype") == 0) {
        extract_int32_from_token(valstr, &val_i32);
        astrid_instrument_set_param_int32(instrument, PARAM_DISTORTION_TYPE, val_i32);
    } else if(strcmp(keystr, "distmix") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_DISTORTION_MIX, val_f);
    } else if(strcmp(keystr, "dist") == 0) {
        extract_float_from_token(valstr, &val_f);
        astrid_instrument_set_param_float(instrument, PARAM_DISTORTION_AMOUNT, 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);


@@ 217,22 264,30 @@ 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, osc_amp, env, in0, in1, p, last_p=-1;
    lpfloat_t osc_pw, osc_saturation, osc_env_speed, 
              osc_mix_ctl, osc_drift_amount, drift, osc_drift_speed,
    lpfloat_t sample, osc_amp, env, in1, in2, d1, d2, p, last_p=-1,
              distortion_amount, distortion_mix,
              osc_pw, osc_saturation, osc_env_speed, 
              osc_drift_amount, drift, osc_drift_speed,
              osc_out1_mix, osc_out2_mix, gate_out1_mix, gate_out2_mix,
              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;
              in1_out1_mix, in1_out2_mix, in2_out1_mix, in2_out2_mix,
              gate_shape, gate_speed, gate_pw, gate_drift_amount, gate_drift_speed,
              gate_drift, gate_saturation, tracked_freq=220.f;
    int32_t octave_spread, octave_offset, gate_reset, gate_repeat, 
            pitch_tracking_is_enabled, distortion_type;
    lppatternbuf_t gate_pattern;
    lpinstrument_t * instrument = (lpinstrument_t *)arg;
    localctx_t * ctx = (localctx_t *)instrument->context;

    if(!instrument->is_running) return 1;

    distortion_amount = astrid_instrument_get_param_float(instrument, PARAM_DISTORTION_AMOUNT, 0.f);
    distortion_mix = astrid_instrument_get_param_float(instrument, PARAM_DISTORTION_MIX, 0.f);
    distortion_type = astrid_instrument_get_param_int32(instrument, PARAM_DISTORTION_TYPE, 0);

    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_out1_mix = astrid_instrument_get_param_float(instrument, PARAM_OSC_TO_OUT1, 0.5f);
    osc_out2_mix = astrid_instrument_get_param_float(instrument, PARAM_OSC_TO_OUT2, 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.001f) * 1000.f + 1.f;


@@ 243,29 298,46 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg
    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_out1_mix = astrid_instrument_get_param_float(instrument, PARAM_GATE_TO_OUT1, 0.5f);
    gate_out2_mix = astrid_instrument_get_param_float(instrument, PARAM_GATE_TO_OUT2, 0.5f);
    gate_reset = astrid_instrument_get_param_int32(instrument, PARAM_GATE_PHASE_RESET, 0);
    gate_repeat = astrid_instrument_get_param_int32(instrument, PARAM_GATE_REPEAT, 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_drift_amount = astrid_instrument_get_param_float(instrument, PARAM_GATE_DRIFT_DEPTH, 0.f);
    gate_drift_speed = astrid_instrument_get_param_float(instrument, PARAM_GATE_DRIFT_SPEED, 0.15f);
    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);

    mic_mix_ctl = astrid_instrument_get_param_float(instrument, PARAM_MIC_MIX, 0.25f);
    in1_out1_mix = astrid_instrument_get_param_float(instrument, PARAM_IN1_TO_OUT1, 0.5f);
    in1_out2_mix = astrid_instrument_get_param_float(instrument, PARAM_IN1_TO_OUT2, 0.5f);
    in2_out1_mix = astrid_instrument_get_param_float(instrument, PARAM_IN2_TO_OUT1, 0.5f);
    in2_out2_mix = astrid_instrument_get_param_float(instrument, PARAM_IN2_TO_OUT2, 0.5f);

    pitch_tracking_is_enabled = astrid_instrument_get_param_int32(instrument, PARAM_MIC_PITCH_TRACKING_ENABLED, 0);

    //gate_drift = LPInterpolation.linear_pos(ctx->gate_drifter, ctx->gate_drifter->phase) * gate_drift_amount;
    ctx->gate_drifter->freq = gate_drift_speed;
    ctx->gate_drifter->minfreq = gate_drift_speed * 0.3f;
    ctx->gate_drifter->maxfreq = gate_drift_speed * 3.f;
    gate_drift = LPShapeOsc.process(ctx->gate_drifter) * gate_drift_amount;
    ctx->gate->window_morph = fmin(gate_shape, 0.999f); // TODO investigate overflows
    ctx->gate->freq = gate_speed;
    ctx->gate->freq = gate_speed + gate_drift;
    ctx->gate->pulsewidth = gate_pw;
    ctx->gate->saturation = gate_saturation;
    LPPulsarOsc.burst_bytes(ctx->gate, gate_pattern.pattern, gate_pattern.length);

    ctx->gate->once = !gate_repeat;


    if(gate_reset) {
        ctx->gate->phase = 0.f;
        astrid_instrument_set_param_int32(instrument, PARAM_GATE_PHASE_RESET, 0);
    }

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

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


@@ 277,12 349,12 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg

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

        for(j=0; j < NUMOSCS; j++) {
            ctx->oscs[j]->saturation = osc_saturation;
            ctx->oscs[j]->pulsewidth = osc_pw;

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


@@ 296,14 368,9 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg
            ctx->env_phases[j] += ctx->env_phaseincs[j] * (1.f+osc_env_speed) * 3.f;

            if(ctx->env_phases[j] >= 1.f) {
                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;


@@ 315,26 382,47 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg
            while(ctx->drifters[j]->phase >= 1.f) ctx->drifters[j]->phase -= 1.f;
        }

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

        // osc output
        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);
        }

        left = right = sample;

        left *= osc_mix(osc_mix_ctl, 0);
        right *= osc_mix(osc_mix_ctl, 1);
        // mic feedback
        in1 = input[0][i] * MIC_ATTENUATION;
        in2 = input[1][i] * MIC_ATTENUATION;

        // mix everything
        output[0][i] += (sample * osc_out1_mix) + (gated_sample * gate_out1_mix) + (in1 * in1_out1_mix) + (in2 * in2_out1_mix);
        output[1][i] += (sample * osc_out2_mix) + (gated_sample * gate_out2_mix) + (in2 * in1_out2_mix) + (in2 * in2_out2_mix);

        // apply distortion
        if(distortion_mix > 0 && distortion_amount > 0) {
            switch(distortion_type) {
                case 0:
                    // foldback distortion
                    d1 = LPFX.fold(output[0][i] * (distortion_amount+1), &ctx->fold1prev, instrument->samplerate);
                    d2 = LPFX.fold(output[1][i] * (distortion_amount+1), &ctx->fold2prev, instrument->samplerate);
                    break;

                case 1:
                    // bitcrushing
                    d1 = LPFX.crush(output[0][i], (1.f-distortion_amount) * 15 + 1);
                    d2 = LPFX.crush(output[1][i], (1.f-distortion_amount) * 15 + 1);
                    break;

                default:
                    break;
            }

        in0 = input[0][i] * mic_mix(mic_mix_ctl, 0) * MIC_ATTENUATION;
        in1 = input[1][i] * mic_mix(mic_mix_ctl, 1) * MIC_ATTENUATION;
            output[0][i] += d1 * distortion_mix;
            output[1][i] += d2 * distortion_mix;
        }

        output[0][i] += left + in0;
        output[1][i] += right + in1;
        // limit
        output[0][i] = LPFX.limit(output[0][i], &ctx->limit1prev, 1.f, 0.2f, ctx->limit1buf);
        output[1][i] = LPFX.limit(output[1][i], &ctx->limit2prev, 1.f, 0.2f, ctx->limit2buf);
    }

    return 0;


@@ 352,9 440,12 @@ int main() {

    // create env window for the footballs
    ctx->env = LPWindow.create(WIN_HANNOUT, 4096);
    ctx->foldprev = 0.f;
    ctx->limitprev = 1.f;
    ctx->limitbuf = LPBuffer.create(1024, 1, (lpfloat_t)SR);
    ctx->fold1prev = 0.f;
    ctx->fold2prev = 0.f;
    ctx->limit1prev = 1.f;
    ctx->limit2prev = 1.f;
    ctx->limit1buf = LPBuffer.create(1024, 1, (lpfloat_t)SR);
    ctx->limit2buf = LPBuffer.create(1024, 1, (lpfloat_t)SR);

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


@@ 391,9 482,12 @@ int main() {
    ctx->gate->freq = 30.f;
    ctx->gate->phase = 0.f;

    ctx->gate_drift_win = LPWindow.create(WIN_HANN, 4096);
    ctx->gate_drifter = LPShapeOsc.create(ctx->gate_drift_win);

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


@@ 416,11 510,15 @@ int main() {
    for(int o=0; o < NUMOSCS; o++) {
        LPPulsarOsc.destroy(ctx->oscs[o]);
        LPBuffer.destroy(ctx->curves[o]);
        LPBuffer.destroy(ctx->drifters[o]);
    }

    LPPulsarOsc.destroy(ctx->gate);
    LPShapeOsc.destroy(ctx->gate_drifter);
    LPBuffer.destroy(ctx->gate_drift_win);
    LPBuffer.destroy(ctx->env);
    LPBuffer.destroy(ctx->limitbuf);
    LPBuffer.destroy(ctx->limit1buf);
    LPBuffer.destroy(ctx->limit2buf);

    free(ctx);


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

def midi_messages(ctx, mtype, mid, mval):
    ctx.log(f'MIDI CALLBACK on instrument: {mtype=} {mid=} {mval=}')
    if mtype == 144 and mid == 60:
        ctx.t.play(0, 'simple').schedule()

def trigger(ctx):
    #return None
    events = []


@@ 14,7 19,7 @@ def update(ctx, k, v):

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

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

M astrid/src/astrid.c => astrid/src/astrid.c +96 -0
@@ 1,5 1,7 @@
#include "astrid.h"

int init_instrument_message(lpmsg_t * msg, char * instrument_name);

static volatile int * astrid_instrument_is_running;
static pthread_mutex_t astrid_nursery_lock = PTHREAD_MUTEX_INITIALIZER;



@@ 1165,6 1167,26 @@ int lpmidi_trigger_notemap(int device_id, int note) {
    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;

    init_instrument_message(&msg, instrument_name);
    msg.type = LPMSG_MIDI_FROM_DEVICE;

    memcpy(msg.msg, &mtype, sizeof(unsigned char));
    offset += sizeof(unsigned char);

    memcpy(msg.msg+offset, &mid, sizeof(unsigned char));
    offset += sizeof(unsigned char);

    memcpy(msg.msg+offset, &mval, sizeof(unsigned char));

    send_play_message(msg);

    return 0;
}

/* VOICES
 * ******/
#if 0


@@ 2613,12 2635,14 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
        }
    }

#if 0
    /* clamp output */
    for(c=0; c < instrument->channels; c++) {
        for(i=0; i < (size_t)nframes; i++) {
            output_channels[c][i] = fmax(-1.f, fmin(output_channels[c][i], 1.f));
        }
    }
#endif

    return 0;
}


@@ 2922,6 2946,14 @@ void * instrument_message_thread(void * arg) {
                if(instrument->trigger != NULL) instrument->trigger(instrument);
                break;

            case LPMSG_MIDI_FROM_DEVICE:
                syslog(LOG_DEBUG, "C MSG: midi from device\n");
                break;

            case LPMSG_MIDI_TO_DEVICE:
                syslog(LOG_DEBUG, "C MSG: midi to device\n");
                break;

            default:
                // Garbage typed messages will shut 'er down
                syslog(LOG_WARNING, "C MSG: got bad msg type %d\n", instrument->msg.type);


@@ 3231,6 3263,55 @@ handle_serial_message_events:
    return 0;
}

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

    lpinstrument_t * instrument = (lpinstrument_t *)arg;

    if((ret = snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0)) < 0) {
        syslog(LOG_ERR, "%s midi listener: Could not open ALSA seq. Error: (%d) %s\n", instrument->name, ret, snd_strerror(ret));
        return 0;
    }

    snd_seq_set_client_name(seq_handle, "astrid_midi_listener");

    if((port = snd_seq_create_simple_port(seq_handle, "input", 
                    SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, 
                    SND_SEQ_PORT_TYPE_APPLICATION)) < 0) {
        syslog(LOG_ERR, "%s midi listener: Could not create ALSA port. Error: (%d) %s\n", instrument->name, port, snd_strerror(port));
        snd_seq_close(seq_handle);
        return 0;
    }

    if((ret = snd_seq_connect_from(seq_handle, port, 20, 0)) < 0) {
        syslog(LOG_ERR, "%s midi listener: Could not connect to ALSA port. Error: (%d) %s\n", instrument->name, ret, snd_strerror(ret));
        snd_seq_close(seq_handle);
        return 0;
    }

    while(instrument->is_running) {
        snd_seq_event_input(seq_handle, &event);
        switch(event->type) {
            case SND_SEQ_EVENT_NOTEON:
                lpmidi_relay_to_instrument(instrument->name, NOTE_ON, event->data.note.note, event->data.note.velocity);
                break;
            case SND_SEQ_EVENT_NOTEOFF:
                lpmidi_relay_to_instrument(instrument->name, NOTE_OFF, event->data.note.note, event->data.note.velocity);
                break;
            case SND_SEQ_EVENT_CONTROLLER:
                lpmidi_relay_to_instrument(instrument->name, CONTROL_CHANGE, event->data.control.param, event->data.control.value);
                break;
            default:
                break;
        }
    }

    snd_seq_close(seq_handle);
    return 0;
}

lpinstrument_t * astrid_instrument_start(
    char * name, 
    int channels, 


@@ 3445,6 3526,13 @@ lpinstrument_t * astrid_instrument_start(
        return NULL;
    }

    /* Start listening for MIDI messages in the midi listener thread */
    // TODO support multiple devices
    if(pthread_create(&instrument->midi_listener_thread, NULL, instrument_midi_listener_thread, (void*)instrument) != 0) {
        syslog(LOG_ERR, "Could not initialize instrument midi listener thread. Error: %s\n", strerror(errno));
        return NULL;
    }

    /* start the cleanup thread */
    if(pthread_create(&instrument->cleanup_thread, NULL, instrument_cleanup_thread, (void*)instrument) != 0) {
        syslog(LOG_ERR, "Could not initialize instrument cleanup thread. Error: %s\n", strerror(errno));


@@ 3506,6 3594,14 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {
        syslog(LOG_ERR, "Error while attempting to join with cleanup thread. Ret: %d Errno: %d (%s)\n", ret, errno, strerror(ret));
    }

    syslog(LOG_DEBUG, "Joining with midi listener thread...\n");
    if((ret = pthread_join(instrument->midi_listener_thread, NULL)) != 0) {
        if(ret == EINVAL) syslog(LOG_ERR, "EINVAL\n");
        if(ret == EDEADLK) syslog(LOG_ERR, "DEADLOCK\n");
        if(ret == ESRCH) syslog(LOG_ERR, "ESRCH\n");
        syslog(LOG_ERR, "Error while attempting to join with midi listener thread. Ret: %d Errno: %d (%s)\n", ret, errno, strerror(ret));
    }

    syslog(LOG_DEBUG, "Joining with serial listener thread...\n");
    if((ret = pthread_join(instrument->serial_listener_thread, NULL)) != 0) {
        if(ret == EINVAL) syslog(LOG_ERR, "EINVAL\n");

M astrid/src/astrid.h => astrid/src/astrid.h +3 -0
@@ 31,6 31,7 @@
#include "pqueue.h"
#include <hiredis/hiredis.h>
#include <jack/jack.h>
#include <alsa/asoundlib.h>

#define NUM_NODES 4096
#define NUM_RENDERERS 10


@@ 180,6 181,7 @@ typedef struct lpinstrument_t {
    pthread_t cleanup_thread;
    pthread_t message_feed_thread;
    pthread_t serial_listener_thread;
    pthread_t midi_listener_thread;
    pthread_t message_scheduler_pq_thread;
    lpscheduler_t * async_mixer;
    lpbuffer_t * lastbuf;


@@ 294,6 296,7 @@ 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);
int lpmidi_relay_to_instrument(char * instrument_name, unsigned char mtype, unsigned char mid, unsigned char mval);

int lpserial_setctl(int device_id, int param_id, size_t value);
int lpserial_getctl(int device_id, int ctl, lpfloat_t * value);

M libpippi/src/oscs.pulsar.c => libpippi/src/oscs.pulsar.c +5 -1
@@ 175,6 175,9 @@ lpfloat_t process_pulsarosc(lppulsarosc_t * p) {
        burst = 0; 
    }

    /* Treat the pulse as a one-shot. Reset the phase to rewind, or toggle once off */
    if(p->phase >= 1.f && p->once) return 0.f;

    /* If there's a non-zero pulsewidth, and the burst value is 1, 
     * then syntesize a pulse */
    if(p->pulsewidth > 0 && burst && p->phase < p->pulsewidth) {


@@ 233,7 236,8 @@ lpfloat_t process_pulsarosc(lppulsarosc_t * p) {
    // about phase boundries (and do things when they happen)
    p->pulse_edge = (p->phase >= 1.f);

    // wrap phases
    // wrap phases unless once is toggled
    if(p->once) return sample;
    if(p->phase < 0) {
        while(p->phase < 0) p->phase += 1.f;
    } else if(p->phase >= 1.f) {

M libpippi/src/oscs.pulsar.h => libpippi/src/oscs.pulsar.h +2 -0
@@ 25,6 25,8 @@ typedef struct lppulsarosc_t {
    size_t burst_size;
    size_t burst_pos; 

    bool once;  /* if once is true, after the phase overflows the osc returns zeros
                   instead of resetting the phase to 0, to use the osc as a one-shot... */
    bool pulse_edge;
    lpfloat_t phase;
    lpfloat_t saturation; /* Probability of all pulses to no pulses */

M libpippi/src/pippiconstants.h => libpippi/src/pippiconstants.h +2 -0
@@ 139,6 139,8 @@ enum LPMessageTypes {
    LPMSG_DATA,
    LPMSG_SHUTDOWN,
    LPMSG_SET_COUNTER,
    LPMSG_MIDI_FROM_DEVICE,
    LPMSG_MIDI_TO_DEVICE,
    NUM_LPMESSAGETYPES
};


M pippi/renderer.pxd => pippi/renderer.pxd +4 -0
@@ 68,8 68,11 @@ cdef extern from "astrid.h":
        LPMSG_SCHEDULE,
        LPMSG_LOAD,
        LPMSG_RENDER_COMPLETE,
        LPMSG_DATA,
        LPMSG_SHUTDOWN,
        LPMSG_SET_COUNTER,
        LPMSG_MIDI_FROM_DEVICE,
        LPMSG_MIDI_TO_DEVICE,
        NUM_LPMESSAGETYPES

    ctypedef struct lpmsg_t:


@@ 136,6 139,7 @@ cdef extern from "astrid.h":
    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)
    int lpmidi_relay_to_instrument(char * instrument_name, unsigned char mtype, unsigned char mid, unsigned char mval)

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


M pippi/renderer.pyx => pippi/renderer.pyx +40 -10
@@ 18,9 18,9 @@ import sys
import time
import warnings

cimport cython
import numpy as np
cimport numpy as np
cimport cython

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


@@ 401,7 401,13 @@ cdef class Instrument:

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

        msgstr = ''
        if self.msg.type != LPMSG_MIDI_FROM_DEVICE:
            # FIXME use the is_encoded flag or something...
            # or maybe I can ignore this and just decode from utf-8 like a criminal...
            msgstr = render_params.decode('ascii')

        graph = None
        if with_graph:
            graph = ugens.Graph()


@@ 494,6 500,23 @@ cdef class Instrument:
        except Exception as e:
            logger.exception('Error during %s update message handling: %s' % (self.name, e))

    def handle_midi_message(self, char * payload):
        cdef EventContext ctx 
        cdef size_t offset = 0
        cdef unsigned char mtype=0, mid=0, mval=0
        if not hasattr(self.renderer, 'midi_messages'):
            logger.warning('Ignoring MIDI message: this instrument has no callback registered')
            return None

        memcpy(&mtype, payload, sizeof(unsigned char))
        offset += sizeof(unsigned char)
        memcpy(&mid, payload+offset, sizeof(unsigned char))
        offset += sizeof(unsigned char)
        memcpy(&mval, payload+offset, sizeof(unsigned char))

        logger.debug(f'PY handle_midi_message: {mtype=} {mid=} {mval=}')
        ctx = self.get_event_context()
        self.renderer.midi_messages(ctx, mtype, mid, mval)

cdef tuple collect_players(Instrument instrument):
    loop = False


@@ 705,7 728,7 @@ def _run_forever(Instrument instrument,
        str instrument_name, 
        int channels, 
        double adc_length,
        object q
        object q, 
    ):
    cdef lpmsg_t msg



@@ 731,6 754,13 @@ def _run_forever(Instrument instrument,
            logger.info('PY MSG: update')
            instrument.handle_update_message(msg.msg.decode('utf-8'))

        elif msg.type == LPMSG_MIDI_FROM_DEVICE:
            logger.info('PY MSG: midi from device')
            instrument.handle_midi_message(msg.msg)

        elif msg.type == LPMSG_MIDI_TO_DEVICE:
            logger.info('PY MSG: midi to device')

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


@@ 751,7 781,7 @@ def _run_forever(Instrument instrument,
                        instrument.reload()
                        instrument.last_reload = last_edit
            except InstrumentError as e:
                logger.error('Error trying to reload instrument. Shutting down...')
                logger.error('PY: Error trying to reload instrument. Shutting down...')
                break

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


@@ 764,11 794,11 @@ def run_forever(str script_path, str instrument_name=None, int channels=2, doubl

    try:
        # Start the stream and setup the instrument
        logger.info(f'loading python instrument... {script_path=} {instrument_name=}')
        logger.info(f'PY: loading python instrument... {script_path=} {instrument_name=}')
        instrument = Instrument(instrument_name, script_path, channels, adc_length)
        logger.info(f'started instrument... {script_path=} {instrument_name=}')
        logger.info(f'PY: started instrument... {script_path=} {instrument_name=}')
    except InstrumentError as e:
        logger.error('Error trying to start instrument. Shutting down...')
        logger.error('PY: Error trying to start instrument. Shutting down...')
        return

    render_pool = []


@@ 786,14 816,14 @@ def run_forever(str script_path, str instrument_name=None, int channels=2, doubl
            # Read messages from the console and relay them to the q
            # Also does memory cleanup on spent shared memory buffers
            if astrid_instrument_tick(instrument.i) < 0:
                logger.info('Could not read console line')
                logger.info('PY: Could not read console line')
                time.sleep(2)
                continue

    except KeyboardInterrupt as e:
        print('Got keyboard interrupt')
        print('PY: Got keyboard interrupt')

    print('Waiting for the render processes to complete')
    print('Shutting down...')
    message_process.join()
    for r in render_pool:
        r.join()

M setup.py => setup.py +1 -1
@@ 26,7 26,7 @@ ext_modules = cythonize([
                'libpippi/src/pippicore.c',
                'astrid/src/astrid.c',
            ],
            libraries=['jack', 'rt'], 
            libraries=['jack', 'rt', 'asound'], 
            include_dirs=INCLUDES+[
                'libpippi/vendor/libpqueue/src', 
                'libpippi/vendor/linenoise',