~hecanjog/pippi

63452f97d36f071cfb2a47ac0d490f013cfaa695 — He Can Jog 2 months ago b7860cf
Cleanup to python astrid instruments, adc work

- Moved Instrument init into main thread
- Python instruments use the same astrid_instrument_tick routine as C
  instruments
- Bugfixes to sequencer priority queue
M astrid/Makefile => astrid/Makefile +1 -1
@@ 109,7 109,7 @@ astrid-pulsar:
astrid-simple:
	mkdir -p build

	$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/simple.c $(LPLIBS) -o build/astrid-simple
	$(CC) $(LPFLAGS) -fsanitize=address -fsanitize=leak -fno-omit-frame-pointer $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/simple.c $(LPLIBS) -o build/astrid-simple


build: clean astrid-q astrid-seriallistener astrid-ipc astrid-devices astrid-midimap astrid-pulsar astrid-simple

M astrid/orc/simple.py => astrid/orc/simple.py +6 -6
@@ 5,13 5,13 @@ def trigger(ctx):

def play(ctx):
    ctx.log('Rendering simple tone!')
    #out = oscs.SineOsc(freq=dsp.rand(200, 1000), amp=dsp.rand(0.1, 0.5)).play(10).env('pluckout')
    #out = out & ctx.adc(out.dur)
    out = ctx.adc(1)
    out = oscs.SineOsc(freq=dsp.rand(200, 1000), amp=dsp.rand(0.1, 0.5)).play(10).env('pluckout')
    out = out & ctx.adc(out.dur).env('pluckout')
    #out = ctx.adc(1)

    if dsp.rand() > 0.5:
        out = fx.fold(out, dsp.rand(1, 2))
    yield out * 0.1
    #if dsp.rand() > 0.5:
    #    out = fx.fold(out, dsp.rand(1, 2))
    yield fx.norm(out, 0.2)

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

M astrid/src/astrid.c => astrid/src/astrid.c +24 -58
@@ 413,13 413,13 @@ int lpipc_setvalue(char * path, void * value, size_t size) {

    /* Get the file descriptor for the shared memory segment */
    if((fd = shm_open(semname, O_RDWR, LPIPC_PERMS)) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not open shared memory segment. (%s) %s\n", semname, strerror(errno));
        syslog(LOG_ERR, "lpipc_setvalue Could not open shared memory segment. (%s) %s\n", semname, strerror(errno));
        return -1;
    }

    /* Attach the shared memory to the pointer */
    if((shmaddr = (void*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not mmap shared memory segment to size %ld. (%s) %s\n", size, semname, strerror(errno));
        syslog(LOG_ERR, "lpipc_setvalue Could not mmap shared memory segment to size %ld. (%s) %s\n", size, semname, strerror(errno));
        return -1;
    }



@@ 493,19 493,19 @@ int lpipc_getvalue(char * path, void ** value) {

    /* Get the file descriptor for the shared memory segment */
    if((fd = shm_open(semname, O_RDWR, LPIPC_PERMS)) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not open shared memory segment. (%s) %s\n", semname, strerror(errno));
        syslog(LOG_ERR, "lpipc_getvalue Could not open shared memory segment. (%s) %s\n", semname, strerror(errno));
        return -1;
    }

    /* Get the size of the segment */
    if(fstat(fd, &statbuf) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not stat shm. Error: %s\n", strerror(errno));
        syslog(LOG_ERR, "lpipc_getvalue Could not stat shm. Error: %s\n", strerror(errno));
        return -1;
    }

    /* Attach the shared memory to the pointer */
    if((shmaddr = (void*)mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not mmap shared memory segment to size %ld. (%s) %s\n", statbuf.st_size, semname, strerror(errno));
        syslog(LOG_ERR, "lpipc_getvalue Could not mmap shared memory segment to size %ld. (%s) %s\n", statbuf.st_size, semname, strerror(errno));
        return -1;
    }



@@ 633,37 633,37 @@ int lpsampler_aquire(char * name, lpbuffer_t ** buf) {

    /* Open the semaphore */
    if((sem = sem_open(path, 0)) == SEM_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_aquire failed to open semaphore %s. Error: %s\n", path, strerror(errno));
        syslog(LOG_ERR, "lpsampler_aquire failed to open semaphore %s. Error: %s\n", path, strerror(errno));
        return -1;
    }

    /* Aquire a lock on the semaphore */
    if(sem_wait(sem) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire failed to decrementsem %s. Error: %s\n", path, strerror(errno));
        syslog(LOG_ERR, "lpsampler_aquire failed to decrementsem %s. Error: %s\n", path, strerror(errno));
        return -1;
    }

    /* Get the file descriptor for the shared memory segment */
    if((fd = shm_open(path, O_RDWR, LPIPC_PERMS)) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not open shared memory segment. (%s) %s\n", path, strerror(errno));
        syslog(LOG_ERR, "lpsampler_aquire Could not open shared memory segment. (%s) %s\n", path, strerror(errno));
        return -1;
    }

    /* Get the size of the segment */
    if(fstat(fd, &statbuf) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not stat shm. Error: %s\n", strerror(errno));
        syslog(LOG_ERR, "lpsampler_aquire Could not stat shm. Error: %s\n", strerror(errno));
        return -1;
    }

    /* Attach the shared memory to the pointer */
    if((*buf = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        syslog(LOG_ERR, "lpipc_buffer_aquire Could not mmap shared memory segment to size %ld. (%s) %s\n", statbuf.st_size, path, strerror(errno));
        syslog(LOG_ERR, "lpsampler_aquire Could not mmap shared memory segment to size %ld. (%s) %s\n", statbuf.st_size, path, strerror(errno));
        return -1;
    }

    /* Clean up sempahore resources */
    if(sem_close(sem) < 0) {
        syslog(LOG_ERR, "lpipc_buffer_aquire sem_close Could not close semaphore\n");
        syslog(LOG_ERR, "lpsampler_aquire sem_close Could not close semaphore\n");
        return -1;
    }



@@ 2280,13 2280,11 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) {
        memset(output_channels[c], 0, nframes * sizeof(float));
    }

#if 0
    /* write the block into the adc ringbuffer */
    if(lpsampler_write_ringbuffer_block(path, input_channels, instrument->channels, nframes) < 0) {
        syslog(LOG_ERR, "Error writing into adc ringbuf\n");
        return 0;
    }
#endif

    /* mix in async renders */
    if(instrument->async_mixer != NULL) {


@@ 2421,8 2419,6 @@ void * instrument_seq_pq(void * arg) {
    }

    syslog(LOG_INFO, "Message scheduler pq thread shutting down...\n");
    pqueue_free(instrument->msgpq);
    free(instrument->pqnodes);
    return 0;
}



@@ 2871,6 2867,17 @@ int astrid_instrument_stop(lpinstrument_t * instrument) {
        return -1;
    }

    /* cleanup the pq memory */
    pqueue_free(instrument->msgpq);
    free(instrument->pqnodes);

    /* and the jack i/o buffers */
    free(instrument->inports);
    free(instrument->outports);

    /* poof! */
    free(instrument);

    syslog(LOG_DEBUG, "All done, see ya later!\n");
    closelog();
    return 0;


@@ 2995,6 3002,7 @@ int astrid_instrument_session_open(lpinstrument_t * instrument) {

int astrid_instrument_session_close(lpinstrument_t * instrument) {
    syslog(LOG_DEBUG, "Closing LMDB session...\n");
    mdb_txn_abort(instrument->dbtxn_read);
	mdb_dbi_close(instrument->dbenv, instrument->dbi);
	mdb_env_close(instrument->dbenv);
    syslog(LOG_DEBUG, "Done cleaning up LMDB...\n");


@@ 3049,55 3057,13 @@ int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * buf
    memcpy(msg.msg, buffer_code, strlen(buffer_code));
    msg.type = LPMSG_RENDER_COMPLETE;
    if(send_play_message(msg) < 0) {
        syslog(LOG_ERR, "COuld not send render complete message. (%d) %s\n", errno, strerror(errno));
        syslog(LOG_ERR, "Could not send render complete message. (%d) %s\n", errno, strerror(errno));
        return 1;
    }

    return 0;
}

int astrid_instrument_console_readline(char * instrument_name) {
    char * cmdline;
    size_t cmdlength;
    lpmsg_t cmd = {0};

    cmdline = linenoise("^_- ");
    if(cmdline == NULL) return 0;

    strncpy(cmd.instrument_name, instrument_name, strlen(instrument_name)+1);
    cmdlength = strnlen(cmdline, ASTRID_MAX_CMDLINE);

    printf("Got a cmd!\n");
    /* write the line into the history */
    linenoiseHistoryAdd(cmdline);

    if(parse_message_from_cmdline(cmdline, cmdlength, &cmd) < 0) {
        syslog(LOG_ERR, "Could not parse message from cmdline %s\n", cmdline);
        return -1;
    }

    free(cmdline);

    if(cmd.type == LPMSG_SERIAL) {
        if(send_serial_message(cmd, cmd.instrument_name) < 0) {
            syslog(LOG_ERR, "Could not send serial message...\n");
            return -1;
        }
    } else {
        printf("Sending the command on the %s q\n", cmd.instrument_name);
        if(send_play_message(cmd) < 0) {
            syslog(LOG_ERR, "Could not send play message...\n");
            return -1;
        }
    }

    if(cmd.type == LPMSG_SHUTDOWN) {
        return 1;
    }
 
    return 0;
}

int astrid_instrument_tick(lpinstrument_t * instrument) {
    char * cmdline;
    size_t cmdlength;

M astrid/src/astrid.h => astrid/src/astrid.h +0 -1
@@ 325,7 325,6 @@ lpfloat_t astrid_instrument_get_param_float(lpinstrument_t * instrument, int par
void astrid_instrument_set_param_float_list(lpinstrument_t * instrument, int param_index, lpfloat_t * value, size_t size);
void astrid_instrument_get_param_float_list(lpinstrument_t * instrument, int param_index, size_t size, lpfloat_t * list);
lpfloat_t astrid_instrument_get_param_float_list_item(lpinstrument_t * instrument, int param_index, size_t size, int item_index, lpfloat_t default_value);
int astrid_instrument_console_readline(char * instrument_name);
int astrid_instrument_tick(lpinstrument_t * instrument);
int astrid_instrument_session_open(lpinstrument_t * instrument);
int astrid_instrument_session_close(lpinstrument_t * instrument);

M pippi/renderer.pxd => pippi/renderer.pxd +2 -1
@@ 153,8 153,8 @@ cdef extern from "astrid.h":
    )

    int astrid_instrument_stop(lpinstrument_t * instrument)
    int astrid_instrument_tick(lpinstrument_t * instrument)
    void scheduler_cleanup_nursery(lpscheduler_t * s)
    int astrid_instrument_console_readline(char * instrument_name)
    int relay_message_to_seq(lpinstrument_t * instrument)
    int send_play_message(lpmsg_t msg)
    int send_serial_message(lpmsg_t msg, char * tty);


@@ 197,6 197,7 @@ cdef class Instrument:
    cdef public size_t last_reload
    cdef public double max_processing_time
    cdef public int default_midi_device
    cdef char * ascii_name
    cdef lpinstrument_t * i
    cpdef EventContext get_event_context(Instrument self, bint with_graph=*)
    cpdef lpmsg_t get_message(Instrument self)

M pippi/renderer.pyx => pippi/renderer.pyx +44 -51
@@ 3,12 3,12 @@
import array
from cpython cimport array
from libc.stdlib cimport calloc, free
from libc.string cimport strcpy, memcpy
from libc.string cimport strcpy, memcpy, strncpy
import logging
from logging.handlers import SysLogHandler
import importlib
import importlib.util
from multiprocessing import Process, Event
from multiprocessing import Process
import os
from pathlib import Path
import platform


@@ 321,9 321,12 @@ cdef class EventContext:
        return self.p._params

cdef class Instrument:
    def __init__(self, str name, str path, int channels, double adc_length):
    def __cinit__(self, str name, str path, int channels, double adc_length):
        instrument_byte_string = name.encode('UTF-8')
        cdef char * _instrument_ascii_name = instrument_byte_string
        # _instrument_ascii_name will get garbage collected at the end of this function
        self.ascii_name = <char *>calloc(LPMAXNAME, sizeof(char))
        strncpy(self.ascii_name, _instrument_ascii_name, LPMAXNAME-1)

        self.name = name
        self.path = path


@@ 331,12 334,15 @@ cdef class Instrument:
        self.last_reload = 0
        self.max_processing_time = 0

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

        self.load_renderer(name, path)

    def __dealloc__(self):
        free(self.ascii_name)

    def load_renderer(self, name, path):
        """ Loads a renderer module from the script 
            at self.path 


@@ 677,46 683,11 @@ cdef int astrid_schedule_python_triggers(Instrument instrument) except -1:
        logger.exception('Error during scheduling of python triggers: %s' % e)
        return -1

def _wait_on_commands_forever(str instrument_name, stop_event):
    # takes console input with linenoise and sends parsed messages 
    # on the instrument message q, or to an external serial device
    instrument_byte_string = instrument_name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string

    while True:
        if stop_event.is_set():
            break

        ret = astrid_instrument_console_readline(_instrument_ascii_name)

        if ret < 0:
            logger.info('Could not read console line')
            time.sleep(0.5)
            continue

        if ret > 0:
            logger.info('Console has signaled stop, shutting down command loop...')
            break

def _run_forever(str script_path, str instrument_name, int channels, double adc_length, stop_event):
    cdef Instrument instrument = None
def _run_forever(Instrument instrument, str script_path, str instrument_name, int channels, double adc_length):
    cdef lpmsg_t msg
    instrument_byte_string = instrument_name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string

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

    # Load the script as a module into the ASTRID_INSTRUMENT global
    try:
        logger.info(f'loading python instrument... {script_path=} {instrument_name=}')
        instrument = Instrument(instrument_name, script_path, channels, adc_length)
        logger.info(f'loaded instrument {instrument=}')
    except InstrumentError as e:
        logger.error('Error trying to start instrument. Shutting down...')
        return

    # Start the stream and setup the instrument
    logger.info(f'started instrument... {script_path=} {instrument_name=}')
    while True:
        logger.info('reading messages...')
        try:


@@ 727,7 698,6 @@ def _run_forever(str script_path, str instrument_name, int channels, double adc_

        if msg.type == LPMSG_SHUTDOWN:
            logger.info('PY MSG: shutdown')
            stop_event.set()
            break

        elif msg.type == LPMSG_UPDATE:


@@ 746,24 716,47 @@ def _run_forever(str script_path, str instrument_name, int channels, double adc_
                logger.error('Error trying to schedule python triggers...')

        elif msg.type == LPMSG_LOAD:
            if instrument is None:
                instrument = Instrument(instrument_name, script_path)
            else:
                last_edit = os.path.getmtime(instrument.path)
                if last_edit > instrument.last_reload:
                    instrument.reload()
                    instrument.last_reload = last_edit
            try:
                if instrument is None:
                    instrument = Instrument(instrument_name, script_path, channels, adc_length)
                else:
                    last_edit = os.path.getmtime(instrument.path)
                    if last_edit > instrument.last_reload:
                        instrument.reload()
                        instrument.last_reload = last_edit
            except InstrumentError as e:
                logger.error('Error trying to reload instrument. Shutting down...')
                break

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

def run_forever(str script_path, str instrument_name=None, int channels=2, double adc_length=30):
    cdef Instrument instrument = None
    instrument_name = instrument_name if instrument_name is not None else Path(script_path).stem
    stop_event = Event()
    render_process = Process(target=_run_forever, args=(script_path, instrument_name, channels, adc_length, stop_event))
    instrument_byte_string = instrument_name.encode('UTF-8')
    cdef char * _instrument_ascii_name = instrument_byte_string

    try:
        # Start the stream and setup the instrument
        logger.info(f'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=}')
    except InstrumentError as e:
        logger.error('Error trying to start instrument. Shutting down...')
        return

    render_process = Process(target=_run_forever, args=(instrument, script_path, instrument_name, channels, adc_length))
    render_process.start()

    try:
        _wait_on_commands_forever(instrument_name, stop_event)
        while instrument.i.is_running:
            # 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')
                time.sleep(2)
                continue

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