~hecanjog/pippi

8a90b50100ef5ed9ac23bcd0b6ae5030c2e27275 — Erik Schoster a month ago 7a72c67
Add new instrument config struct, improvements to sampler interface
4 files changed, 304 insertions(+), 136 deletions(-)

M astrid/src/astrid.c
M astrid/src/astrid.h
M pippi/renderer.pxd
M pippi/renderer.pyx
M astrid/src/astrid.c => astrid/src/astrid.c +163 -71
@@ 580,7 580,7 @@ int lpipc_destroyvalue(char * path) {
 * BUFFER TOOLS
 * ************/
int lpsampler_get_path(char * name, char * path) {
    snprintf(path, PATH_MAX, "/astrid-sampler-%s", name);
    snprintf(path, PATH_MAX, "/lpsamp-%s", name);
    //syslog(LOG_ERR, "name=%s path=%s PATH_MAX=%d\n", name, path, PATH_MAX);
    return 0;
}


@@ 2031,6 2031,8 @@ int encode_update_message_param_int32(uint16_t id, char * valstr, lpmsg_t * msg)

// Takes the cmd on the instrument and runs each param through the 
// instrument's param update callback.
// TODO think about handling large / special values, like buffers...
// payload could be the shm ID like render complete messages...?
int process_param_updates(lpinstrument_t * instrument) {
    char cmdline[LPMAXMSG] = {0};
    char * paramline;


@@ 2038,6 2040,9 @@ int process_param_updates(lpinstrument_t * instrument) {
    char * valtoken;
    char * cmdline_save;
    char * paramline_save;
    uint32_t key_hash = 0;
    float val_f = 0;
    int32_t val_i32 = 0;

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



@@ 2048,8 2053,37 @@ int process_param_updates(lpinstrument_t * instrument) {
            valtoken = strtok_r(NULL, "=", &paramline_save);
            if(valtoken != NULL) {
                syslog(LOG_DEBUG, "UPDATE Key: %s, Value: %s\n", keytoken, valtoken);
                // TODO think about handling large / special values, like buffers...
                // payload could be the shm ID like render complete messages...?
                // first process built-in param updates
                /*
                 * name-volume: float - post-resampler volume (1.0)
                 * name-resampler-rec: int - resampler record toggle (1)
                 * name-resampler-dub: int - resampler overdub toggle (0)
                 * name-resampler-pos: float - resampler write pos from 0 to 1 in loop bounds (0)
                 * name-resampler-clear: int - value ignored, clear the buffer
                 * name-resampler-feedback: float - feedback amount from 0 to 1 (0.25)
                 * */
                key_hash = lphashstr(keytoken);
                if(key_hash == instrument->param_volume) {
                    extract_float_from_token(valtoken, &val_f);
                    astrid_instrument_set_param_float(instrument, instrument->param_volume, val_f);
                } else if(key_hash == instrument->resampler_param_rec) {
                    extract_int32_from_token(valtoken, &val_i32);
                    astrid_instrument_set_param_int32(instrument, instrument->resampler_param_rec, val_i32);
                } else if(key_hash == instrument->resampler_param_dub) {
                    extract_int32_from_token(valtoken, &val_i32);
                    astrid_instrument_set_param_int32(instrument, instrument->resampler_param_dub, val_i32);
                } else if(key_hash == instrument->resampler_param_pos) {
                    extract_float_from_token(valtoken, &val_f);
                    astrid_instrument_set_param_float(instrument, instrument->resampler_param_pos, val_f);
                } else if(key_hash == instrument->resampler_param_clear) {
                    astrid_instrument_set_param_int32(instrument, instrument->resampler_param_clear, 1);
                } else if(key_hash == instrument->resampler_param_feedback) {
                    extract_float_from_token(valtoken, &val_f);
                    astrid_instrument_set_param_float(instrument, instrument->resampler_param_feedback, val_f);
                }

                // process the update callback if there is one
                if(instrument->update == NULL) continue;
                if(instrument->update(instrument, keytoken, valtoken) < 0) {
                    syslog(LOG_ERR, "process_param_updates: failed to pass param (%s=%s) to update callback.\n", keytoken, valtoken);
                }


@@ 2155,6 2189,8 @@ int parse_message_from_args(int argc, int arg_offset, char * argv[], lpmsg_t * m
    strncpy(msg->instrument_name, instrument_name, instrument_name_length);
    strncpy(msg->msg, message_params, bytesread);

    printf("msgtype=%c instrument_name=%s message_params=%s\n", msgtype, instrument_name, message_params);

    if(prepare_and_send_instrument_message(msg, msgtype) < 0) {
        syslog(LOG_ERR, "Could not prepare message of type %c: (%d) %s\n", msgtype, errno, strerror(errno));
        return -1;


@@ 2683,6 2719,7 @@ 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;
    int c;



@@ 2734,21 2771,24 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
    }

    /* write the output block into the resampler ringbuffer */
    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;
    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;
        }
    }

    /* clamp output and apply post fader volume  */
    syslog(LOG_DEBUG, "Clamping output. %s\n", instrument->name);
    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] * instrument->volume, 1.f));
            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);
    return 0;
}


@@ 3008,13 3048,10 @@ void * instrument_message_thread(void * arg) {

            case LPMSG_UPDATE:
                syslog(LOG_DEBUG, "C MSG: update\n");
                if(instrument->update == NULL) continue;
                // decode each update param and call the callback with it
                if(process_param_updates(instrument) < 0) {
                    syslog(LOG_ERR, "Could not encode update messages...\n");
                    continue;
                }
                //instrument->update(instrument);
                break;

            case LPMSG_PLAY:


@@ 3506,23 3543,20 @@ void * instrument_midi_output_thread(void * arg) {
    return 0;
}

lpinstrument_config_t astrid_instrument_init_config(char * name) {
    lpinstrument_config_t config = {0};
    config.name = name;
    config.input_channels = 2;
    config.output_channels = 2;
    config.initial_volume = 1.f;
    config.requested_samplerate = -1.f;
    config.is_interactive = 1;
    config.adc_length = 60.f;
    config.resampler_length = 60.f;
    return config;
}

lpinstrument_t * astrid_instrument_start(
    char * name, 
    int input_channels, 
    int output_channels, 
    int ext_relay_enabled,
    double adc_length,
    double resampler_length,
    void * ctx,
    char * tty,
    char * midiin_device_name,
    char * midiout_device_name,
    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_from_config(lpinstrument_config_t config) {
    lpinstrument_t * instrument;
    struct sigaction shutdown_action;
    jack_status_t jack_status;


@@ 3532,37 3566,39 @@ lpinstrument_t * astrid_instrument_start(
    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));
    openlog(name, LOG_PID, LOG_USER);
    syslog(LOG_DEBUG, "starting %s instrument...\n", name);

    instrument->name = name;
    instrument->volume = 1.f; // TODO accept default initial volume on config
    instrument->input_channels = input_channels;
    instrument->output_channels = output_channels;
    instrument->context = ctx;

    instrument->stream = stream;
    instrument->renderer = renderer;
    instrument->update = update;
    instrument->trigger = trigger;
    instrument->ext_relay_enabled = ext_relay_enabled;
    openlog(config.name, LOG_PID, LOG_USER);
    syslog(LOG_DEBUG, "starting %s instrument...\n", config.name);

    instrument->name = config.name;
    instrument->initial_volume = config.initial_volume;
    instrument->input_channels = config.input_channels;
    instrument->output_channels = config.output_channels;
    instrument->context = config.ctx;
    instrument->is_interactive = config.is_interactive;

    instrument->stream = config.stream_callback;
    instrument->renderer = config.renderer_callback;
    instrument->update = config.update_callback;
    instrument->trigger = config.trigger_callback;
    instrument->ext_relay_enabled = config.ext_relay_enabled;
    instrument->is_ready = 1;

    instrument->midiin_device_id = -1;
    if(midiin_device_name != NULL) {
        instrument->midiin_device_id = lpmidi_get_device_id_by_name(midiin_device_name);
        syslog(LOG_DEBUG, "Set MIDI input device ID (%d) for %s\n", instrument->midiin_device_id, midiin_device_name);
    if(config.midiin_device_name != NULL) {
        instrument->midiin_device_id = lpmidi_get_device_id_by_name(config.midiin_device_name);
        syslog(LOG_DEBUG, "Set MIDI input device ID (%d) for %s\n", instrument->midiin_device_id, config.midiin_device_name);
    }

    instrument->midiout_device_id = -1;
    if(midiout_device_name != NULL) {
        instrument->midiout_device_id = lpmidi_get_device_id_by_name(midiout_device_name);
        syslog(LOG_DEBUG, "Set MIDI output device ID (%d) for %s\n", instrument->midiout_device_id, midiout_device_name);
    if(config.midiout_device_name != NULL) {
        instrument->midiout_device_id = lpmidi_get_device_id_by_name(config.midiout_device_name);
        syslog(LOG_DEBUG, "Set MIDI output device ID (%d) for %s\n", instrument->midiout_device_id, config.midiout_device_name);
    }

    /* Seed the random number generator */


@@ 3575,12 3611,12 @@ lpinstrument_t * astrid_instrument_start(
    astrid_instrument_is_running = &instrument->is_running;

    if(sigaction(SIGINT, &shutdown_action, NULL) == -1) {
        syslog(LOG_ERR, "%s Could not init SIGINT signal handler. Error: %s\n", name, strerror(errno));
        syslog(LOG_ERR, "%s Could not init SIGINT signal handler. Error: %s\n", instrument->name, strerror(errno));
        exit(1);
    }

    if(sigaction(SIGTERM, &shutdown_action, NULL) == -1) {
        syslog(LOG_ERR, "%s Could not init SIGTERM signal handler. Error: %s\n", name, strerror(errno));
        syslog(LOG_ERR, "%s Could not init SIGTERM signal handler. Error: %s\n", instrument->name, strerror(errno));
        exit(1);
    }



@@ 3590,8 3626,8 @@ lpinstrument_t * astrid_instrument_start(
    snprintf(instrument->midiout_message_q_name, NAME_MAX, "/%s-midiout", instrument->name);

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



@@ 3601,11 3637,11 @@ lpinstrument_t * astrid_instrument_start(

    // set up the voice counter and the buffer counter
    if(lpcounter_create("voiceid") < 0) {
        syslog(LOG_ERR, "%s Could not create voiceid counter. Error: %s\n", name, strerror(errno));
        syslog(LOG_ERR, "%s Could not create voiceid counter. Error: %s\n", instrument->name, strerror(errno));
        exit(1);
    }
    if(lpcounter_create("bufferid") < 0) {
        syslog(LOG_ERR, "%s Could not create bufferid counter. Error: %s\n", name, strerror(errno));
        syslog(LOG_ERR, "%s Could not create bufferid counter. Error: %s\n", instrument->name, strerror(errno));
        exit(1);
    }



@@ 3613,18 3649,18 @@ lpinstrument_t * astrid_instrument_start(
    astrid_instrument_session_open(instrument);

    /* Set up JACK */
    instrument->inports = (jack_port_t **)calloc(input_channels, sizeof(jack_port_t *));
    instrument->outports = (jack_port_t **)calloc(output_channels, sizeof(jack_port_t *));
    instrument->jack_client = jack_client_open(name, jack_options, &jack_status, NULL);
    instrument->inports = (jack_port_t **)calloc(config.input_channels, sizeof(jack_port_t *));
    instrument->outports = (jack_port_t **)calloc(config.output_channels, sizeof(jack_port_t *));
    instrument->jack_client = jack_client_open(config.name, jack_options, &jack_status, NULL);

    syslog(LOG_DEBUG, "JACK STATUS %d\n", (int)jack_status);

    if(instrument->jack_client == NULL) {
        syslog(LOG_ERR, "%s Could not open jack client. Client is NULL: %s\n", name, strerror(errno));
        syslog(LOG_ERR, "%s Could not open jack client. Client is NULL: %s\n", instrument->name, strerror(errno));
        if((jack_status & JackServerFailed) == JackServerFailed) {
            syslog(LOG_ERR, "%s Could not open jack client. Jack server failed with status %2.0x\n", name, jack_status);
            syslog(LOG_ERR, "%s Could not open jack client. Jack server failed with status %2.0x\n", instrument->name, jack_status);
        } else {
            syslog(LOG_ERR, "%s Could not open jack client. Unknown error: %s\n", name, strerror(errno));
            syslog(LOG_ERR, "%s Could not open jack client. Unknown error: %s\n", instrument->name, strerror(errno));
        }
        goto astrid_instrument_shutdown_with_error;
    }


@@ 3642,25 3678,25 @@ lpinstrument_t * astrid_instrument_start(

    /* 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 < output_channels; c++) {
    for(c=0; c < instrument->output_channels; c++) {
        snprintf(outport_name, sizeof(outport_name), "out%d", c);
        instrument->outports[c] = jack_port_register(instrument->jack_client, outport_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    }

    for(c=0; c < input_channels; c++) {
    for(c=0; c < instrument->input_channels; c++) {
        snprintf(inport_name, sizeof(inport_name), "in%d", c);
        instrument->inports[c] = jack_port_register(instrument->jack_client, inport_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
    }

    /* Request some ports from jack */
    for(c=0; c < output_channels; c++) {
    for(c=0; c < instrument->output_channels; c++) {
        if(instrument->outports[c] == NULL) {
            syslog(LOG_ERR, "No more JACK output ports available, shutting down...\n");
            goto astrid_instrument_shutdown_with_error;
        }
    }

    for(c=0; c < input_channels; c++) {
    for(c=0; c < instrument->input_channels; c++) {
        if(instrument->inports[c] == NULL) {
            syslog(LOG_ERR, "No more JACK input ports available, shutting down...\n");
            goto astrid_instrument_shutdown_with_error;


@@ 3669,7 3705,7 @@ lpinstrument_t * astrid_instrument_start(

    /* activate the jack client */
    if(jack_activate(instrument->jack_client) != 0) {
        syslog(LOG_ERR, "%s Could not activate JACK client, shutting down...\n", name);
        syslog(LOG_ERR, "%s Could not activate JACK client, shutting down...\n", instrument->name);
        goto astrid_instrument_shutdown_with_error;
    }



@@ 3710,22 3746,39 @@ lpinstrument_t * astrid_instrument_start(

    /* 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, adc_length, instrument->input_channels, instrument->samplerate)) == NULL) {
    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");
        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, resampler_length, instrument->output_channels, instrument->samplerate)) == NULL) {
    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");
        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); 


    /* Register that everything is running and ready */
    instrument->is_running = 1;
    syslog(LOG_INFO, "%s is running...\n", name);
    syslog(LOG_INFO, "%s is running...\n", instrument->name);

    /* Open the message queues */
    if((instrument->msgq = astrid_msgq_open(instrument->qname)) == (mqd_t) -1) {


@@ 3806,6 3859,40 @@ astrid_instrument_shutdown_with_error:
    return NULL;
}

lpinstrument_t * astrid_instrument_start(
    char * name, 
    int input_channels, 
    int output_channels, 
    int ext_relay_enabled,
    double adc_length,
    double resampler_length,
    void * ctx,
    char * tty,
    char * midiin_device_name,
    char * midiout_device_name,
    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)
) {
    syslog(LOG_WARNING, "astrid_instrument_start is deprecated.\nuse astrid_instrument_start_from_config instead.");
    lpinstrument_config_t config = astrid_instrument_init_config(name);
    config.input_channels = input_channels;
    config.output_channels = output_channels;
    config.ext_relay_enabled = ext_relay_enabled;
    config.adc_length = adc_length;
    config.resampler_length = resampler_length;
    config.ctx = ctx;
    config.tty = tty;
    config.midiin_device_name = midiin_device_name;
    config.midiout_device_name = midiout_device_name;
    config.stream_callback = stream;
    config.renderer_callback = renderer;
    config.update_callback = update;
    config.trigger_callback = trigger;
    return astrid_instrument_start_from_config(config);
}

int astrid_instrument_stop(lpinstrument_t * instrument) {
    int c, ret;



@@ 4177,6 4264,11 @@ int astrid_instrument_tick(lpinstrument_t * instrument) {
    size_t prompt_len = strlen("^_- ") + strlen(instrument->name) + strlen(": ") + 1;
    char prompt[LPMAXNAME+10] = {0};

    if(instrument->is_interactive == 0) {
        usleep((useconds_t)500);
        return 0;
    }

    snprintf(prompt, prompt_len, "^_- %s: ", instrument->name);

    if(instrument->is_running == 0) return 0;


@@ 4188,8 4280,8 @@ int astrid_instrument_tick(lpinstrument_t * instrument) {

    FD_ZERO(&readfds);
    FD_SET(instrument->cmdstate.ifd, &readfds);
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    timeout.tv_sec = 0;
    timeout.tv_usec = 1000;

    if((ret = select(instrument->cmdstate.ifd+1, &readfds, NULL, NULL, &timeout)) < 0) {
        syslog(LOG_ERR, "astrid_instrument_tick: select error (%d) %s\n", errno, strerror(errno));

M astrid/src/astrid.h => astrid/src/astrid.h +36 -1
@@ 143,17 143,43 @@ typedef struct lpscheduler_t {
    lpevent_t * nursery_head;
} lpscheduler_t;

typedef struct lpinstrument_config_t {
    char * name;
    int input_channels;
    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;
    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);
} lpinstrument_config_t;

typedef struct lpinstrument_t {
    char * name;
    int input_channels;
    int output_channels;
    lpfloat_t volume;
    int is_interactive;
    lpfloat_t initial_volume;
    volatile int is_running; // threads, pools, loops, etc
    volatile int is_waiting; // for lpmsg_t messages
    volatile int is_ready;   // for console commands
    int has_been_initialized;
    lpfloat_t samplerate;

    int mixbus_count;
    int mixbus_handles[64]; // who needs more than 64 busses?

    // linenoise cmd buf
    struct linenoiseState cmdstate;
    char cmdbuf[1024];


@@ 168,11 194,18 @@ typedef struct lpinstrument_t {
    // the XDG config dir where LMDB sessions live
    char datapath[PATH_MAX]; 

    uint32_t param_volume;

    // The adc ringbuf name
    char adcname[PATH_MAX];
    lpbuffer_t * adcbuf; // mmaped pointer to adcbuf
    char resamplername[PATH_MAX];
    lpbuffer_t * resamplerbuf; // mmaped pointer to adcbuf
    uint32_t resampler_param_rec,  // precomputed hashes for instrument resampler params
             resampler_param_dub,  // hash of <resampler_name>-rec, etc
             resampler_param_pos,
             resampler_param_clear, 
             resampler_param_feedback;

    // The instrument message q(s)
    char qname[NAME_MAX]; 


@@ 386,6 419,8 @@ lpinstrument_t * astrid_instrument_start(
        int (*trigger)(void * instrument)
    );

lpinstrument_config_t astrid_instrument_init_config(char * name);
lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t config);
int astrid_instrument_stop(lpinstrument_t * instrument);

lpparamset_t astrid_instrument_create_paramset(char * paramset_defs);

M pippi/renderer.pxd => pippi/renderer.pxd +29 -18
@@ 135,16 135,43 @@ cdef extern from "astrid.h":
        int bank_lsb
        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)

    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]


@@ 206,23 233,8 @@ cdef extern from "astrid.h":
    lpbuffer_t * deserialize_buffer(char * str, lpmsg_t * msg)
    int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * bufstr, size_t size)

    lpinstrument_t * astrid_instrument_start(
        const char * name, 
        int input_channels, 
        int output_channels, 
        int ext_relay_enabled,
        double adc_length,
        double resampler_length,
        void * ctx, 
        char * tty,
        char * midi_input_device_name,
        char * midi_output_device_name,
        int (*stream)(size_t blocksize, float ** input, float ** output, void * instrument),
        int (*renderer)(void * instrument),
        int (*update)(void * instrument),
        int (*trigger)(void * instrument)
    )

    lpinstrument_config_t astrid_instrument_init_config(char * name)
    lpinstrument_t * astrid_instrument_start_from_config(lpinstrument_config_t config)
    int astrid_instrument_stop(lpinstrument_t * instrument)
    int astrid_instrument_tick(lpinstrument_t * instrument)
    void scheduler_cleanup_nursery(lpscheduler_t * s)


@@ 282,7 294,6 @@ cdef class Instrument:
    cpdef EventContext get_event_context(Instrument self, str msgstr=*, bint with_graph=*)
    cpdef lpmsg_t get_message(Instrument self)
    cdef SoundBuffer read_from_adc(Instrument self, double length, double offset=*, int channels=*, int samplerate=*)
    cdef SoundBuffer read_from_resampler(Instrument self, double length, double offset=*, int channels=*, int samplerate=*, str instrument=*)
    cdef SoundBuffer read_block_from_sampler(Instrument self, str name, double length, double offset=*, int channels=*, int samplerate=*)
    cdef SoundBuffer read_from_sampler(Instrument self, str name)
    cdef void save_to_sampler(Instrument self, str name, SoundBuffer snd)

M pippi/renderer.pyx => pippi/renderer.pyx +76 -46
@@ 312,7 312,8 @@ cdef class SessionParamBucket:
        cdef int32_t val_i32
        cdef float val_f

        if key not in self.instrument.instrument_param_type_map:
        if self.instrument.instrument_param_type_map is None or \
                key not in self.instrument.instrument_param_type_map:
            logger.warning('Unknown param "%s"' % key)
            return



@@ 394,29 395,36 @@ cdef class EventContext:
            channels = self.instrument.input_channels
        return self.instrument.read_from_adc(length, offset=offset, channels=channels)

    def sample(self, 
            str name, 
            SoundBuffer snd=None, 
            double length=0, 
            double offset=0, 
            int channels=2, 
            bint overdub=False, 
            bint wrap=False, 
            bint overwrite=False
        ):
        # read operations
        if snd is None:
            if length <= 0 and offset == 0:
                # just read the entire sample
                return self.instrument.read_from_sampler(name)
    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
            logger.error(f'Reading entire sample {name}')
            return self.instrument.read_from_sampler(name)

        if channels <= 0:
            channels = self.instrument.output_channels

            # otherwise read a portion from the sample
            return self.instrument.read_block_from_sampler(name, length, offset=offset, channels=channels)
        # otherwise read a portion from the sample
        logger.error(f'Reading {length=} {offset=} sample {name}')
        return self.instrument.read_block_from_sampler(name, length, offset=offset, channels=channels)

    def save_sample(self, str name, SoundBuffer snd):
        return self.instrument.save_to_sampler(name, snd)

    def dub_into_sample(self, str name, SoundBuffer snd, double offset=0, bint overdub=False, bint wrap=False):
        return self.instrument.write_block_into_sampler(name, snd, offset, overdub, wrap)

    def sample(self, str name, SoundBuffer snd=None, double length=0, double offset=0, int channels=2, bint overdub=False, bint wrap=False, bint overwrite=False):
        # FIXME: maybe get rid of this API, it's confusing
        if snd is None:
            return self.load_sample(name, length, offset, channels)

        # write operations
        if overwrite:        
            logger.error(f'Overwrite sample {name}')
            return self.instrument.save_to_sampler(name, snd)
        else:
            logger.error(f'Dub into sample {name} {overdub=} {wrap=}')
            return self.instrument.write_block_into_sampler(name, snd, offset, overdub, wrap)

    def resample(self, length=1, offset=0, channels=2, samplerate=48000, instrument=None):


@@ 439,16 447,20 @@ cdef class Instrument:
            str path, 
            int input_channels, 
            int output_channels, 
            double initial_volume,
            double adc_length, 
            double resampler_length, 
            str midi_input_device_name, 
            str midi_output_device_name
            str midi_output_device_name,
            int is_interactive
        ):
        cdef char * midi_input_device_cstr
        cdef char * midi_output_device_cstr
        self.midi_input_device_name = NULL
        self.midi_output_device_name = NULL

        cdef lpinstrument_config_t config

        if midi_input_device_name is not None:
            midi_input_device_name_byte_string = midi_input_device_name.encode('UTF-8')
            midi_input_device_cstr = midi_input_device_name_byte_string


@@ 467,28 479,25 @@ cdef class Instrument:
        self.ascii_name = <char *>calloc(LPMAXNAME, sizeof(char))
        strncpy(self.ascii_name, _instrument_ascii_name, LPMAXNAME-1)

        config = astrid_instrument_init_config(self.ascii_name)

        self.name = name
        self.path = path
        self.cache = None
        self.last_reload = 0
        self.max_processing_time = 0

        self.i = astrid_instrument_start(
            self.ascii_name, 
            input_channels, 
            output_channels,
            1,  # ext relay enabled
            adc_length, 
            resampler_length, 
            NULL,  # ctx
            NULL,  # tty
            self.midi_input_device_name,
            self.midi_output_device_name,
            NULL,  # c stream callback
            NULL,  # c renderer callback
            NULL,  # c update callback
            NULL   # c trigger callback
        )
        config.initial_volume = initial_volume
        config.input_channels = input_channels
        config.output_channels = output_channels
        config.midiin_device_name = self.midi_input_device_name
        config.midiout_device_name = self.midi_output_device_name
        config.adc_length = adc_length
        config.resampler_length = resampler_length
        config.ext_relay_enabled = 1
        config.is_interactive = is_interactive

        self.i = astrid_instrument_start_from_config(config)
        if self.i == NULL:
            raise InstrumentError('Could not initialize lpinstrument_t')



@@ 618,7 627,6 @@ cdef class Instrument:

        out = LPBuffer.create(length_in_frames, self.input_channels, samplerate)

        # fixme add dcblock
        if lpsampler_read_ringbuffer_block(self.i.adcname, self.i.adcbuf, offset_in_frames, out) < 0:
            logger.error('pippi.renderer ADC read: failed to read %d frames at offset %d from ADC' % (length_in_frames, offset_in_frames))
            return snd


@@ 639,17 647,12 @@ cdef class Instrument:

        name_bytes = name.encode('UTF-8')
        cdef char * _name = name_bytes

        out = lpsampler_aquire_and_map(_name)

        if out == NULL or out.length <= 0 or out.channels <= 0:
        if out == NULL:
            logger.error('pippi.renderer sampler read: failed to read from %s' % _name)
            if out != NULL:
                logger.error('length=%s channels=%s' % (out.length, out.channels))
            return None

        snd = SoundBuffer(framelength=out.length, channels=self.output_channels, samplerate=self.samplerate)
        logger.debug('out.length=%s len(snd)=%s self.samplerate=%s' % (out.length, len(snd), self.samplerate))

        if len(snd) == 0:
            logger.error('sampler BLOWUP! len(snd)==0 self.output_channels=%d self.samplerate=%s' % (self.output_channels, self.samplerate))


@@ 678,6 681,8 @@ cdef class Instrument:
        cdef size_t offset_in_frames = <size_t>(offset * samplerate)

        bank = lpsampler_aquire_and_map(_name)
        if bank == NULL:
            return None

        start = (bank.pos - offset_in_frames - length_in_frames) % bank.length
        for i in range(length_in_frames):


@@ 1064,6 1069,8 @@ def _run_forever(Instrument instrument,
        str instrument_name, 
        int input_channels, 
        int output_channels, 
        int is_interactive,
        double initial_volume,
        double adc_length,
        double resampler_length,
        object q, 


@@ 1110,7 1117,15 @@ def _run_forever(Instrument instrument,
            # FIXME pass this along to the comrades
            try:
                if instrument is None:
                    instrument = Instrument(instrument_name, script_path, input_channels, output_channels, adc_length, resampler_length)
                    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:


@@ 1130,8 1145,10 @@ def run_forever(
        int output_channels=2, 
        double adc_length=60, 
        double resampler_length=60, 
        double initial_volume=1,
        str midi_input_device_name=None, 
        str midi_output_device_name=None
        str midi_output_device_name=None,
        int is_interactive=1,
    ):
    cdef Instrument instrument = None
    instrument_name = instrument_name if instrument_name is not None else Path(script_path).stem


@@ 1150,10 1167,12 @@ def run_forever(
            script_path, 
            input_channels, 
            output_channels,
            initial_volume,
            adc_length, 
            resampler_length, 
            midi_input_device_name, 
            midi_output_device_name
            midi_output_device_name,
            is_interactive
        )
        logger.info(f'PY: started instrument... {script_path=} {instrument_name=}')
    except InstrumentError as e:


@@ 1167,7 1186,18 @@ def run_forever(
        comrade.start()
        render_pool += [ comrade ]

    message_process = Process(target=_run_forever, args=(instrument, script_path, instrument_name, input_channels, output_channels, adc_length, resampler_length, render_q))
    message_process = Process(target=_run_forever, args=(
        instrument, 
        script_path, 
        instrument_name, 
        input_channels, 
        output_channels, 
        is_interactive,
        initial_volume, 
        adc_length, 
        resampler_length, 
        render_q
    ))
    message_process.start()

    try: