~hecanjog/pippi

555ce23a6bed1eb74e837cca5345eba0542ece5c — Erik Schoster a month ago 8870a48
astrid midi & sampler, pippi rhythm, lpbuffer

astrid midi:
- support different input/output MIDI devices per instrument

astrid sampler:
- ctx.sample fixes

pippi rhythm:
- start modifying rhythm.seq to support astrid instrument streaming

libpippi lpbuffer_t:
- add LPBuffer.dub_into
M astrid/Makefile => astrid/Makefile +10 -2
@@ 29,7 29,7 @@ LPDBINCLUDES = -I${LPDIR}/vendor/sqlite3
LPFLAGS = -g -std=gnu2x -Wall -Wextra -pedantic -O0 -DNOPYTHON
LPLIBS = -lm -ldl -lpthread -lrt -ljack -lasound

default: littlefield
default: astrid-sampler

clean:
	rm -rf build/*


@@ 57,7 57,6 @@ astrid-ipc:
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcgetvalue.c $(LPLIBS) -o build/astrid-ipcgetvalue
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcsetvalue.c $(LPLIBS) -o build/astrid-ipcsetvalue
	#gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/ipcdestroyvalue.c $(LPLIBS) -o build/astrid-ipcdestroyvalue
	gcc $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/sampler.c $(LPLIBS) -o build/astrid-sampler

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


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

astrid-sampler:
	mkdir -p build

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

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


astrid-paramset:
	mkdir -p build


M astrid/src/astrid.c => astrid/src/astrid.c +24 -13
@@ 3369,7 3369,7 @@ void * instrument_midi_listener_thread(void * arg) {

    lpinstrument_t * instrument = (lpinstrument_t *)arg;

    syslog(LOG_DEBUG, "%s MIDI listener thread starting for device ID %d\n", instrument->name, instrument->midi_device_id);
    syslog(LOG_DEBUG, "%s MIDI listener thread starting for device ID %d\n", instrument->name, instrument->midiin_device_id);

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


@@ 3386,7 3386,7 @@ void * instrument_midi_listener_thread(void * arg) {
        return 0;
    }

    if((ret = snd_seq_connect_from(seq_handle, port, instrument->midi_device_id, 0)) < 0) {
    if((ret = snd_seq_connect_from(seq_handle, port, instrument->midiin_device_id, 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;


@@ 3422,7 3422,7 @@ void * instrument_midi_output_thread(void * arg) {

    lpinstrument_t * instrument = (lpinstrument_t *)arg;

    syslog(LOG_ERR, "%s MIDI output thread starting for device ID %d\n", instrument->name, instrument->midi_device_id);
    syslog(LOG_ERR, "%s MIDI output thread starting for device ID %d\n", instrument->name, instrument->midiout_device_id);

    if((instrument->midioutmsgq = astrid_msgq_open(instrument->midiout_message_q_name)) == (mqd_t) -1) {
        syslog(LOG_CRIT, "Could not open midiout msgq for instrument %s. Error: %s\n", instrument->name, strerror(errno));


@@ 3445,7 3445,7 @@ void * instrument_midi_output_thread(void * arg) {
        return 0;
    }

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


@@ 3467,7 3467,7 @@ void * instrument_midi_output_thread(void * arg) {
            usleep((useconds_t)10000);
            continue;
        }
        syslog(LOG_ERR, "%s MIDI output thread got msg for device ID %d\n", instrument->name, instrument->midi_device_id);
        syslog(LOG_ERR, "%s MIDI output thread got msg for device ID %d\n", instrument->name, instrument->midiout_device_id);

        if(snd_midi_event_encode(midi_event, (unsigned char *)msg.msg, sizeof(unsigned char) * 3, &event) < 0) {
            syslog(LOG_ERR, "%s midi output: Could not decode MIDI message from lpmsg_t body. Error: (%d) %s\n", instrument->name, errno, strerror(errno));


@@ 3505,7 3505,8 @@ lpinstrument_t * astrid_instrument_start(
    double resampler_length,
    void * ctx,
    char * tty,
    char * midi_device_name,
    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),


@@ 3523,7 3524,7 @@ lpinstrument_t * astrid_instrument_start(
    instrument = (lpinstrument_t *)LPMemoryPool.alloc(1, sizeof(lpinstrument_t));
    memset(instrument, 0, sizeof(lpinstrument_t));

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



@@ 3538,10 3539,16 @@ lpinstrument_t * astrid_instrument_start(
    instrument->ext_relay_enabled = ext_relay_enabled;
    instrument->is_ready = 1;

    instrument->midi_device_id = -1;
    if(midi_device_name != NULL) {
        instrument->midi_device_id = lpmidi_get_device_id_by_name(midi_device_name);
        syslog(LOG_DEBUG, "Set MIDI device ID (%d) for %s\n", instrument->midi_device_id, midi_device_name);
    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);
    }

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

    /* Seed the random number generator */


@@ 3730,13 3737,15 @@ lpinstrument_t * astrid_instrument_start(

    /* Start listening for MIDI messages in the midi listener thread */
    // TODO support multiple devices
    if(instrument->midi_device_id >= 0) {
    if(instrument->midiin_device_id >= 0) {
        syslog(LOG_DEBUG, "We're gonna listen for some MIDI over here in %s-land!\n", instrument->name);
        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;
        }
    }

    if(instrument->midiout_device_id >= 0) {
        if(pthread_create(&instrument->midi_output_thread, NULL, instrument_midi_output_thread, (void*)instrument) != 0) {
            syslog(LOG_ERR, "Could not initialize instrument midi output thread. Error: %s\n", strerror(errno));
            return NULL;


@@ 3804,7 3813,7 @@ 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));
    }

    if(instrument->midi_device_id >= 0) {
    if(instrument->midiin_device_id >= 0) {
        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");


@@ 3812,7 3821,9 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {
            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));
        }
    }

    if(instrument->midiout_device_id >= 0) {
        syslog(LOG_DEBUG, "Joining with midi output thread...\n");
        if((ret = pthread_join(instrument->midi_output_thread, NULL)) != 0) {
            if(ret == EINVAL) syslog(LOG_ERR, "EINVAL\n");

M astrid/src/astrid.h => astrid/src/astrid.h +4 -2
@@ 185,7 185,8 @@ typedef struct lpinstrument_t {
    lpmsg_t msg;
    lpmsg_t cmd;

    int midi_device_id;
    int midiin_device_id;
    int midiout_device_id;

    int tty_is_enabled;
    char tty_path[NAME_MAX]; 


@@ 374,7 375,8 @@ lpinstrument_t * astrid_instrument_start(
        double resampler_length, 
        void * ctx, 
        char * tty, 
        char * midi_device_name, 
        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), 

M libpippi/src/pippicore.c => libpippi/src/pippicore.c +45 -1
@@ 41,6 41,7 @@ lpbuffer_t * concat_buffers(lpbuffer_t * a, lpbuffer_t * b);
int buffers_are_equal(lpbuffer_t * a, lpbuffer_t * b);
int buffers_are_close(lpbuffer_t * a, lpbuffer_t * b, int d);
void dub_buffer(lpbuffer_t * a, lpbuffer_t * b, size_t start);
void dub_into_buffer(lpbuffer_t * buf, lpbuffer_t * src, size_t offset, lpfloat_t feedback, int wrap, int overdub);
void dub_scalar(lpbuffer_t * a, lpfloat_t, size_t start);
void env_buffer(lpbuffer_t * buf, lpbuffer_t * env);
lpbuffer_t * pad_buffer(lpbuffer_t * buf, size_t before, size_t after); 


@@ 128,7 129,7 @@ lprand_t LPRand = { LOGISTIC_SEED_DEFAULT, LOGISTIC_X_DEFAULT, \
    rand_base_stdlib, rand_rand, rand_randint, rand_randbool, rand_choice };
lpmemorypool_factory_t LPMemoryPool = { 0, 0, 0, memorypool_init, memorypool_custom_init, memorypool_alloc, memorypool_custom_alloc, memorypool_free };
const lparray_factory_t LPArray = { create_array, create_array_from, destroy_array };
const lpbuffer_factory_t LPBuffer = { create_buffer, create_buffer_from_float, create_buffer_from_bytes, copy_buffer, clone_buffer, clear_buffer, split2_buffer, scale_buffer, min_buffer, max_buffer, mag_buffer, play_buffer, pan_stereo_buffer, mix_buffers, remix_buffer, clip_buffer, cut_buffer, cut_into_buffer, varispeed_buffer, resample_buffer, multiply_buffer, scalar_multiply_buffer, add_buffers, scalar_add_buffer, subtract_buffers, scalar_subtract_buffer, divide_buffers, scalar_divide_buffer, concat_buffers, buffers_are_equal, buffers_are_close, dub_buffer, dub_scalar, env_buffer, pad_buffer, taper_buffer, trim_buffer, fill_buffer, repeat_buffer, reverse_buffer, resize_buffer, plot_buffer, destroy_buffer };
const lpbuffer_factory_t LPBuffer = { create_buffer, create_buffer_from_float, create_buffer_from_bytes, copy_buffer, clone_buffer, clear_buffer, split2_buffer, scale_buffer, min_buffer, max_buffer, mag_buffer, play_buffer, pan_stereo_buffer, mix_buffers, remix_buffer, clip_buffer, cut_buffer, cut_into_buffer, varispeed_buffer, resample_buffer, multiply_buffer, scalar_multiply_buffer, add_buffers, scalar_add_buffer, subtract_buffers, scalar_subtract_buffer, divide_buffers, scalar_divide_buffer, concat_buffers, buffers_are_equal, buffers_are_close, dub_buffer, dub_into_buffer, dub_scalar, env_buffer, pad_buffer, taper_buffer, trim_buffer, fill_buffer, repeat_buffer, reverse_buffer, resize_buffer, plot_buffer, destroy_buffer };
const lpinterpolation_factory_t LPInterpolation = { interpolate_linear_pos, interpolate_linear_pos2, interpolate_linear, interpolate_linear_channel, interpolate_hermite_pos, interpolate_hermite };
const lpparam_factory_t LPParam = { param_create_from_float, param_create_from_int };
const lpwavetable_factory_t LPWavetable = { create_wavetable, create_wavetable_stack, destroy_wavetable };


@@ 1044,6 1045,49 @@ void dub_buffer(lpbuffer_t * a, lpbuffer_t * b, size_t start) {
    }
}

void dub_into_buffer(
        lpbuffer_t * buf, 
        lpbuffer_t * src, 
        size_t offset, 
        lpfloat_t feedback,
        int wrap,
        int overdub
    ) {
    size_t i, j, dublength, slop=0;
    int c, d;
    lpfloat_t sample;

    dublength = src->length;
    slop = (src->length + offset) - buf->length;
    if(slop > 0) dublength -= slop;

    if(overdub == 0) {
        memset(buf->data+(offset*buf->channels), 0, sizeof(lpfloat_t) * buf->channels * dublength);
    }

    for(i=0; i < dublength; i++) {
        for(c=0; c < buf->channels; c++) {
            d = c % src->channels;
            sample = buf->data[(i+offset) * buf->channels + c] * feedback;
            sample += src->data[i * src->channels + d];
            buf->data[(i+offset) * buf->channels + c] = sample;
        }
    }

    if(wrap == 1 && slop > 0) {
        for(j=0; j < slop; j++) {
            for(c=0; c < buf->channels; c++) {
                d = c % src->channels;
                sample = buf->data[j * buf->channels + c] * feedback;
                sample += src->data[(i+j) * src->channels + d];
                buf->data[j * buf->channels + c] = sample;
            }
        }
    }
}



void dub_scalar(lpbuffer_t * a, lpfloat_t val, size_t start) {
    int c;


M libpippi/src/pippicore.h => libpippi/src/pippicore.h +1 -0
@@ 124,6 124,7 @@ typedef struct lpbuffer_factory_t {
    int (*buffers_are_equal)(lpbuffer_t *, lpbuffer_t *);
    int (*buffers_are_close)(lpbuffer_t *, lpbuffer_t *, int);
    void (*dub)(lpbuffer_t *, lpbuffer_t *, size_t);
    void (*dub_into)(lpbuffer_t * buf, lpbuffer_t * src, size_t offset, lpfloat_t feedback, int wrap, int overdub);
    void (*dub_scalar)(lpbuffer_t *, lpfloat_t, size_t);
    void (*env)(lpbuffer_t *, lpbuffer_t *);
    lpbuffer_t * (*pad)(lpbuffer_t * buf, size_t before, size_t after);

M pippi/renderer.pxd => pippi/renderer.pxd +5 -2
@@ 185,7 185,8 @@ cdef extern from "astrid.h":
        double resampler_length,
        void * ctx, 
        char * tty,
        char * midi_device_name,
        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),


@@ 243,7 244,8 @@ cdef class Instrument:
    cdef public double samplerate
    cdef public int default_midi_device
    cdef char * ascii_name # instrument name as a c string
    cdef char * midi_device_name
    cdef char * midi_input_device_name
    cdef char * midi_output_device_name
    cdef lpinstrument_t * i
    cpdef EventContext get_event_context(Instrument self, str msgstr=*, bint with_graph=*)
    cpdef lpmsg_t get_message(Instrument self)


@@ 252,6 254,7 @@ cdef class 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)
    cdef SoundBuffer write_block_into_sampler(Instrument self, str name, SoundBuffer snd, double offset, bint overdub=*, bint wrap=*)

cdef class SessionParamBucket:
    cdef Instrument instrument

M pippi/renderer.pyx => pippi/renderer.pyx +94 -23
@@ 373,15 373,21 @@ cdef class EventContext:
    def adc(self, length=1, offset=0, channels=2):
        return self.instrument.read_from_adc(length, offset=offset, channels=channels)

    def sample(self, str name, SoundBuffer snd=None, length=0, offset=0, channels=2):
        if snd is None and length > 0:
            # read a portion from the sample
    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)

            # otherwise read a portion from the sample
            return self.instrument.read_block_from_sampler(name, length, offset=offset, channels=channels)
        elif snd is None:
            # read the entire sample
            return self.instrument.read_from_sampler(name)
        # write to the samplebank FIXME, should be able to use this like dub()
        self.instrument.save_to_sampler(name, snd)

        # write operations
        if overwrite:        
            return self.instrument.save_to_sampler(name, snd)
        else:
            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):
        return self.instrument.read_from_resampler(length, offset=offset, channels=channels, samplerate=samplerate, instrument=instrument)


@@ 393,14 399,23 @@ cdef class EventContext:
        return self.p._params

cdef class Instrument:
    def __cinit__(self, str name, str path, int channels, double adc_length, double resampler_length, str midi_device_name):
        cdef char * midi_device_cstr
        self.midi_device_name = NULL
        if midi_device_name is not None:
            midi_device_name_byte_string = midi_device_name.encode('UTF-8')
            midi_device_cstr = midi_device_name_byte_string
            self.midi_device_name = <char *>calloc(LPMAXNAME, sizeof(char))
            strncpy(self.midi_device_name, midi_device_cstr, LPMAXNAME-1)
    def __cinit__(self, str name, str path, int channels, double adc_length, double resampler_length, str midi_input_device_name, str midi_output_device_name):
        cdef char * midi_input_device_cstr
        cdef char * midi_output_device_cstr
        self.midi_input_device_name = NULL
        self.midi_output_device_name = NULL

        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
            self.midi_input_device_name = <char *>calloc(LPMAXNAME, sizeof(char))
            strncpy(self.midi_input_device_name, midi_input_device_cstr, LPMAXNAME-1)

        if midi_output_device_name is not None:
            midi_output_device_name_byte_string = midi_output_device_name.encode('UTF-8')
            midi_output_device_cstr = midi_output_device_name_byte_string
            self.midi_output_device_name = <char *>calloc(LPMAXNAME, sizeof(char))
            strncpy(self.midi_output_device_name, midi_output_device_cstr, LPMAXNAME-1)

        instrument_byte_string = name.encode('UTF-8')
        cdef char * _instrument_ascii_name = instrument_byte_string


@@ 415,7 430,8 @@ cdef class Instrument:
        self.max_processing_time = 0

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


@@ 451,13 467,15 @@ cdef class Instrument:
                    renderer._ = None

                self.renderer = renderer
                ctx = self.get_event_context()

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

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

                if hasattr(self.renderer, 'MIDI_DEVICE'):


@@ 595,7 613,7 @@ cdef class Instrument:
            return None

        snd = SoundBuffer(framelength=out.length, channels=self.channels, samplerate=self.samplerate)
        logger.debug('out.length=%s len(snd)=%s self.samplerate=%s' % (out.length, len(snd), self.samplerate))
        logger.error('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.channels=%d self.samplerate=%s' % (self.channels, self.samplerate))


@@ 635,6 653,8 @@ cdef class Instrument:
        return snd

    cdef void save_to_sampler(Instrument self, str name, SoundBuffer snd):
        """ Overwrite the entire sampler bank with the SoundBuffer
        """
        cdef size_t i
        cdef int c
        cdef lpbuffer_t * out


@@ 648,13 668,64 @@ cdef class Instrument:
        logger.debug('out.length=%s len(snd)=%s' % (out.length, len(snd)))

        # write the data into it
        for i in range(out.length):
        for i in range(len(snd.frames)):
            for c in range(out.channels):
                out.data[i * out.channels + c] = snd.frames[i,c]

        if lpsampler_release_and_unmap(_name, out) < 0:
            logger.error('Could not release %s sampler memory' % _name)

    cdef SoundBuffer write_block_into_sampler(Instrument self, str name, SoundBuffer snd, double offset, bint overdub=False, bint wrap=False):
        """ Write or dub into a portion of the sampler bank
        """
        cdef size_t framelength, frameoffset, endpoint, i=0, slop=0, last_pos=0
        cdef int c, channels
        cdef lpbuffer_t * buf

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

        # open the shm buffer
        buf = lpsampler_aquire_and_map(_name)
        if buf == NULL:
            buf = lpsampler_create(_name, snd.dur, snd.channels, <int>self.samplerate)

        framelength = <size_t>len(snd.frames) - 1
        frameoffset = <size_t>int(offset * buf.samplerate)
        endpoint = frameoffset + framelength
        slop = buf.length - (frameoffset + framelength) - 1

        if not wrap and slop > 0:
            framelength = buf.length - frameoffset

        # write the data into it
        if overdub:
            for i in range(len(snd.frames)):
                for c in range(buf.channels):
                    buf.data[(i+frameoffset) * buf.channels + c] += snd.frames[i, (c % snd.channels)]

            if wrap and slop > 0:
                last_pos = i
                for i in range(slop):
                    for c in range(buf.channels):
                        buf.data[i * buf.channels + c] += snd.frames[(i+last_pos), (c % snd.channels)]

        else:
            for i in range(len(snd.frames)):
                for c in range(buf.channels):
                    buf.data[(i+frameoffset) * buf.channels + c] = snd.frames[i, (c % snd.channels)]

            if wrap and slop > <size_t>0:
                last_pos = i
                for i in range(slop):
                    for c in range(buf.channels):
                        buf.data[i * buf.channels + c] = snd.frames[(i+last_pos), (c % snd.channels)]

        if lpsampler_release_and_unmap(_name, buf) < 0:
            logger.error('Could not release %s sampler memory' % _name)

        return self.read_from_sampler(name)


    def reload(self):
        logger.debug('Reloading instrument %s from %s' % (self.name, self.path))


@@ 995,7 1066,7 @@ def _run_forever(Instrument instrument,

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

def run_forever(str script_path, str instrument_name=None, int channels=2, double adc_length=30, double resampler_length=30, str midi_device_name=None):
def run_forever(str script_path, str instrument_name=None, int channels=2, double adc_length=30, double resampler_length=30, str midi_input_device_name=None, str midi_output_device_name=None):
    cdef Instrument instrument = None
    instrument_name = instrument_name if instrument_name is not None else Path(script_path).stem
    instrument_byte_string = instrument_name.encode('UTF-8')


@@ 1003,8 1074,8 @@ 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'PY: loading python instrument... {script_path=} {instrument_name=} {midi_device_name=}')
        instrument = Instrument(instrument_name, script_path, channels, adc_length, resampler_length, midi_device_name)
        logger.info(f'PY: loading python instrument... {script_path=} {instrument_name=} {midi_input_device_name=} {midi_output_device_name=}')
        instrument = Instrument(instrument_name, script_path, channels, adc_length, resampler_length, midi_input_device_name, midi_output_device_name)
        logger.info(f'PY: started instrument... {script_path=} {instrument_name=}')
    except InstrumentError as e:
        logger.error('PY: Error trying to start instrument. Shutting down...')

M pippi/rhythm.pyx => pippi/rhythm.pyx +11 -1
@@ 115,6 115,9 @@ cdef class Seq:
            smear=smear,
        )

    def set(self, *args, **kwargs):
        return self.add(*args, **kwargs)

    def update(self, name, **kwargs):
        self.instruments[name].update(kwargs)



@@ 230,7 233,14 @@ cdef class Seq:
        
        return pattern

    def play(self, int numbeats, str patseq=None, bint stems=False, str stemsdir='', str stemsext='wav', bint pool=False):
    def get_events(self):
        cdef list events = []
        return events

    def play(self, *args, **kwargs):
        self.render(*args, **kwargs)

    def render(self, int numbeats, str patseq=None, bint stems=False, str stemsdir='', str stemsext='wav', bint pool=False):
        cdef SoundBuffer out = SoundBuffer()
        cdef dict instrument
        cdef str expanded

M pippi/soundbuffer.pyx => pippi/soundbuffer.pyx +3 -3
@@ 281,7 281,7 @@ cdef class SoundBuffer:
               int samplerate=DEFAULT_SAMPLERATE, 
            object filename=None, 
               int offset=0, 
       double[:,:] buf=None, int framelength=-1):
       double[:,:] buf=None, size_t framelength=-1):
        self.samplerate = samplerate
        self.channels = max(channels, 1)



@@ 289,9 289,9 @@ cdef class SoundBuffer:
            framelength = len(frames)

        if length >= 0:
            framelength = <int>(length * self.samplerate)
            framelength = <size_t>(length * self.samplerate)
        elif framelength >= 0:
            length = framelength * samplerate
            length = framelength * self.samplerate
        elif filename is None and frames is None and buf is None:
            length = 0