M astrid/src/astrid.c => astrid/src/astrid.c +148 -76
@@ 318,7 318,7 @@ int lpipc_getid(char * path) {
return -1;
}
- if(fstat(fd, &st) == -1) {
+if(fstat(fd, &st) == -1) {
syslog(LOG_ERR, "lpipc_getid fstat: %s. Error: %s\n", path, strerror(errno));
close(fd);
return -1;
@@ 1206,10 1206,11 @@ int lpmidi_trigger_notemap(int device_id, int note) {
msg.initiated = now;
- syslog(LOG_INFO, "Sending message from lpmidi trigger notemap\ninitiated %f\nscheduled %f\ncompleted %f\nmax_processing_time %f\nonset_delay %ld\nvoice_id %ld\ncount %ld\ntype %d\nmsg %s\nname %s\n\n",
+ syslog(LOG_INFO, "Sending message from lpmidi trigger notemap\ninitiated %f\nscheduled %f\ninterval %f\noffset %f\nmax_processing_time %f\nonset_delay %ld\nvoice_id %ld\ncount %ld\ntype %d\nmsg %s\nname %s\n\n",
msg.initiated,
msg.scheduled,
- msg.completed,
+ msg.interval,
+ msg.offset,
msg.max_processing_time,
msg.onset_delay,
msg.voice_id,
@@ 1256,6 1257,25 @@ int lpmidi_relay_to_instrument(char * instrument_name, unsigned char mtype, unsi
return 0;
}
+int lpmidi_encode_eventbytes(unsigned char buf[3], int channel, unsigned char message_type, int param, int value) {
+ unsigned char status=0;
+ memset(buf, 0, sizeof(unsigned char) * 3);
+
+ status = message_type | (channel & 0x0F);
+
+ buf[0] = status;
+ buf[1] = param;
+ buf[2] = value;
+
+ return 0;
+}
+
+int lpmidi_encode_msg(lpmsg_t * msg, int channel, unsigned char message_type, int param, int value) {
+ memset(msg->msg, 0, LPMAXMSG);
+ lpmidi_encode_eventbytes((unsigned char *)msg->msg, channel, message_type, param, value);
+ return 0;
+}
+
int lpmidi_get_device_id_by_name(const char * device_name) {
snd_seq_t * seq_handle;
snd_seq_client_info_t * cinfo;
@@ 2171,40 2191,6 @@ int parse_message_from_cmdline(char * cmdline, size_t cmdlength, lpmsg_t * msg)
return 0;
}
-int midi_triggerq_open() {
- int qfd;
-
- umask(0);
- if(mkfifo(ASTRID_MIDI_TRIGGERQ_PATH, S_IRUSR | S_IWUSR | S_IWGRP) == -1 && errno != EEXIST) {
- syslog(LOG_ERR, "midi_triggerq_open mkfifo: Error creating msg queue FIFO. Error: %s\n", strerror(errno));
- return -1;
- }
-
- if((qfd = open(ASTRID_MIDI_TRIGGERQ_PATH, O_RDWR)) < 0) {
- syslog(LOG_ERR, "midi_triggerq_open open: Error opening msg queue FIFO. Error: %s\n", strerror(errno));
- return -1;
- };
-
- return qfd;
-}
-
-int midi_triggerq_schedule(int qfd, lpmidievent_t t) {
- if(write(qfd, &t, sizeof(lpmidievent_t)) != sizeof(lpmidievent_t)) {
- syslog(LOG_ERR, "midi_triggerq_schedule write: Could not write to q. Error: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
-int midi_triggerq_close(int qfd) {
- if(close(qfd) == -1) {
- syslog(LOG_ERR, "midi_triggerq_close close: Error closing msg queue FIFO. Error: %s\n", strerror(errno));
- return -1;
- }
-
- return 0;
-}
/* SCHEDULING
@@ 2583,19 2569,6 @@ void lpscheduler_tick(lpscheduler_t * s) {
void scheduler_schedule_event(lpscheduler_t * s, lpbuffer_t * buf, size_t onset_delay) {
lpevent_t * e;
- /*
- if(s->nursery_head != NULL) {
- e = s->nursery_head;
- s->nursery_head = (void *)e->next;
- e->next = NULL;
- } else {
- e = (lpevent_t *)LPMemoryPool.alloc(1, sizeof(lpevent_t));
- s->event_count += 1;
- e->id = s->event_count;
- e->onset = 0;
- e->callback_onset = 0;
- }
- */
e = (lpevent_t *)LPMemoryPool.alloc(1, sizeof(lpevent_t));
s->event_count += 1;
e->id = s->event_count;
@@ 2607,8 2580,7 @@ void scheduler_schedule_event(lpscheduler_t * s, lpbuffer_t * buf, size_t onset_
e->onset = s->ticks + onset_delay;
syslog(LOG_INFO, "scheduling event ID %ld\n", e->id);
- syslog(LOG_INFO, "scheduler got buffer with onset %ld\n", e->onset);
- syslog(LOG_INFO, "scheduler got buffer value 10 %f\n", buf->data[10]);
+ syslog(LOG_INFO, " with onset %ld\n", e->onset);
start_waiting(s, e);
}
@@ 2936,6 2908,8 @@ void * instrument_message_thread(void * arg) {
//double processing_time_so_far, onset_delay_in_seconds, now=0;
lpinstrument_t * instrument = (lpinstrument_t *)arg;
int is_scheduled = 0;
+ double now = 0.f;
+ size_t onset_delay, ticks, grid_interval=0;
instrument->is_waiting = 1;
while(instrument->is_running) {
@@ 2946,12 2920,14 @@ void * instrument_message_thread(void * arg) {
}
is_scheduled = ((instrument->msg.flags & LPFLAG_IS_SCHEDULED) == LPFLAG_IS_SCHEDULED);
- syslog(LOG_DEBUG, "C MSG: name=%s\n", instrument->msg.instrument_name);
- syslog(LOG_DEBUG, "C MSG: scheduled=%f\n", instrument->msg.scheduled);
- syslog(LOG_DEBUG, "C MSG: voice_id=%d\n", (int)instrument->msg.voice_id);
- syslog(LOG_DEBUG, "C MSG: type=%d\n", (int)instrument->msg.type);
- syslog(LOG_DEBUG, "C MSG: flags=%d\n", (int)instrument->msg.flags);
- syslog(LOG_DEBUG, "C MSG: is_scheduled=%d\n", is_scheduled);
+ syslog(LOG_INFO, "C MSG: name=%s\n", instrument->msg.instrument_name);
+ syslog(LOG_INFO, "C MSG: scheduled=%f\n", instrument->msg.scheduled);
+ syslog(LOG_INFO, "C MSG: interval=%f\n", instrument->msg.interval);
+ syslog(LOG_INFO, "C MSG: offset=%f\n", instrument->msg.offset);
+ syslog(LOG_INFO, "C MSG: voice_id=%d\n", (int)instrument->msg.voice_id);
+ syslog(LOG_INFO, "C MSG: type=%d\n", (int)instrument->msg.type);
+ syslog(LOG_INFO, "C MSG: flags=%d\n", (int)instrument->msg.flags);
+ syslog(LOG_INFO, "C MSG: is_scheduled=%d\n", is_scheduled);
// Handle shutdown early
if(instrument->msg.type == LPMSG_SHUTDOWN) {
@@ 2995,32 2971,26 @@ void * instrument_message_thread(void * arg) {
// Now the fun stuff
switch(instrument->msg.type) {
case LPMSG_RENDER_COMPLETE:
- /* FIXME do this in another thread? */
// Renders from the internal callback AND/OR external renderers (AKA python)
if((buf = deserialize_buffer(instrument->msg.msg, &bufmsg)) == NULL) {
syslog(LOG_ERR, "DAC could not deserialize buffer. Error: (%d) %s\n", errno, strerror(errno));
continue;
}
- // tracking the render time is still useful... this might not be right place tho
- /*
- if(lpscheduler_get_now_seconds(&now) < 0) {
- syslog(LOG_ERR, "Could not get now seconds for loop retriggering\n");
- now = 0;
+ /* Schedule the buffer for playback */
+ lpscheduler_get_now_seconds(&now);
+ onset_delay = 0;
+ if(bufmsg.interval > 0) {
+ ticks = (size_t)(now * instrument->samplerate);
+ grid_interval = (size_t)(bufmsg.interval * instrument->samplerate);
+ onset_delay = grid_interval - (ticks % grid_interval);
}
- processing_time_so_far = now - instrument->msg.initiated;
- onset_delay_in_seconds = instrument->msg.scheduled - processing_time_so_far;
- if(onset_delay_in_seconds < 0) onset_delay_in_seconds = 0.f;
-
- instrument->msg.onset_delay = (size_t)(onset_delay_in_seconds * ASTRID_SAMPLERATE);
-
- syslog(LOG_INFO, "msg.onset_delay %ld\n", instrument->msg.onset_delay);
- */
+ if(bufmsg.offset > 0) {
+ onset_delay += (size_t)(bufmsg.offset * instrument->samplerate);
+ }
- /* Schedule the buffer for playback */
- syslog(LOG_INFO, "RENDER COMPLETE: scheduling buffer with value 10 %f\n", buf->data[10]);
- scheduler_schedule_event(instrument->async_mixer, buf, 0);
+ scheduler_schedule_event(instrument->async_mixer, buf, onset_delay);
//scheduler_debug(instrument->async_mixer);
break;
@@ 3077,6 3047,10 @@ void * instrument_message_thread(void * arg) {
case LPMSG_MIDI_TO_DEVICE:
syslog(LOG_DEBUG, "C MSG: midi to device\n");
+ // put onto the internal midiout q
+ if(send_message(instrument->midiout_message_q_name, instrument->msg) < 0) {
+ syslog(LOG_ERR, "Could not send midi message... (%d) %s\n", errno, strerror(errno));
+ }
break;
default:
@@ 3439,6 3413,90 @@ void * instrument_midi_listener_thread(void * arg) {
return 0;
}
+void * instrument_midi_output_thread(void * arg) {
+ lpmsg_t msg = {0};
+ snd_seq_t * seq_handle;
+ snd_midi_event_t * midi_event;
+ snd_seq_event_t event;
+ int ret, port;
+
+ 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);
+
+ 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));
+ return NULL;
+ }
+ syslog(LOG_ERR, "Opened midiout message queue for %s with fd %d\n", instrument->name, instrument->midioutmsgq);
+
+ if((ret = snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_OUTPUT, 0)) < 0) {
+ syslog(LOG_ERR, "%s midi output: Could not open ALSA seq. Error: (%d) %s\n", instrument->name, ret, snd_strerror(ret));
+ return 0;
+ }
+
+ snd_seq_set_client_name(seq_handle, &instrument->midiout_message_q_name[1]);
+
+ if((port = snd_seq_create_simple_port(seq_handle, "output",
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_APPLICATION)) < 0) {
+ syslog(LOG_ERR, "%s midi output: 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_to(seq_handle, port, instrument->midi_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;
+ }
+
+ if(snd_midi_event_new(256, &midi_event) < 0) {
+ syslog(LOG_ERR, "%s midi output: Could not create MIDI event. Error: (%d) %s\n", instrument->name, ret, snd_strerror(ret));
+ snd_seq_close(seq_handle);
+ return 0;
+ }
+
+ snd_midi_event_init(midi_event);
+
+ while(instrument->is_running) {
+ snd_seq_ev_clear(&event);
+
+ if(astrid_msgq_read(instrument->midioutmsgq, &msg) == (mqd_t) -1) {
+ syslog(LOG_ERR, "%s midi output: Could not read message from midi output q. Error: (%d) %s\n", instrument->name, errno, strerror(errno));
+ 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);
+
+ 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));
+ usleep((useconds_t)10000);
+ continue;
+ }
+
+ //syslog(LOG_ERR, "mtype=%d\n", midi_event->type);
+
+ snd_seq_ev_set_source(&event, port);
+ snd_seq_ev_set_subs(&event);
+ snd_seq_ev_set_direct(&event);
+
+ if((ret = snd_seq_event_output_direct(seq_handle, &event)) < 0) {
+ syslog(LOG_ERR, "%s midi output: Could not send midi event. Error: (%d) %s\n", instrument->name, errno, strerror(errno));
+ usleep((useconds_t)10000);
+ continue;
+ }
+ }
+
+ snd_seq_close(seq_handle);
+
+ syslog(LOG_DEBUG, "Closing midiout message queue...\n");
+ if(instrument->midioutmsgq != (mqd_t) -1) astrid_msgq_close(instrument->midioutmsgq);
+
+ return 0;
+}
+
+
lpinstrument_t * astrid_instrument_start(
char * name,
int channels,
@@ 3508,6 3566,7 @@ lpinstrument_t * astrid_instrument_start(
// Set the message q names
snprintf(instrument->qname, NAME_MAX, "/%s-msgq", instrument->name);
snprintf(instrument->serial_message_q_name, NAME_MAX, "/%s-serial-msgq", instrument->name);
+ snprintf(instrument->midiout_message_q_name, NAME_MAX, "/%s-midiout", instrument->name);
// Set the tty path
if(tty != NULL) {
@@ 3632,7 3691,7 @@ lpinstrument_t * astrid_instrument_start(
instrument->is_running = 1;
syslog(LOG_INFO, "%s is running...\n", name);
- /* Open the message queue */
+ /* Open the message queues */
if((instrument->msgq = astrid_msgq_open(instrument->qname)) == (mqd_t) -1) {
syslog(LOG_CRIT, "Could not open msgq for instrument %s. Error: %s\n", instrument->name, strerror(errno));
return NULL;
@@ 3677,6 3736,11 @@ lpinstrument_t * astrid_instrument_start(
syslog(LOG_ERR, "Could not initialize instrument midi listener thread. Error: %s\n", strerror(errno));
return NULL;
}
+
+ 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;
+ }
}
/* start the cleanup thread */
@@ 3748,6 3812,14 @@ 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));
}
+
+ 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");
+ 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 output thread. Ret: %d Errno: %d (%s)\n", ret, errno, strerror(ret));
+ }
}
syslog(LOG_DEBUG, "Joining with serial listener thread...\n");
M astrid/src/astrid.h => astrid/src/astrid.h +41 -20
@@ 64,15 64,19 @@
#define ASTRID_MAX_PARAMS 4096
#ifndef NOTE_ON
-#define NOTE_ON 144
+#define NOTE_ON 0x90
#endif
#ifndef NOTE_OFF
-#define NOTE_OFF 128
+#define NOTE_OFF 0x80
#endif
#ifndef CONTROL_CHANGE
-#define CONTROL_CHANGE 176
+#define CONTROL_CHANGE 0xB0
+#endif
+
+#ifndef PROGRAM_CHANGE
+#define PROGRAM_CHANGE 0xC0
#endif
#define SPACE ' '
@@ 96,13 100,13 @@ typedef struct lpmidievent_t {
double onset;
double now;
double length;
- char type;
- char note;
- char velocity;
- char program;
- char bank_msb;
- char bank_lsb;
- char channel;
+ int type;
+ int note;
+ int velocity;
+ int program;
+ int bank_msb;
+ int bank_lsb;
+ int channel;
} lpmidievent_t;
/* These events are what is stored in the
@@ 172,10 176,12 @@ typedef struct lpinstrument_t {
char qname[NAME_MAX];
char external_relay_name[NAME_MAX]; // just python, really
char serial_message_q_name[NAME_MAX];
+ char midiout_message_q_name[NAME_MAX];
int ext_relay_enabled;
mqd_t msgq;
mqd_t exmsgq;
mqd_t serialmsgq;
+ mqd_t midioutmsgq;
lpmsg_t msg;
lpmsg_t cmd;
@@ 194,6 200,7 @@ typedef struct lpinstrument_t {
pthread_t message_feed_thread;
pthread_t serial_listener_thread;
pthread_t midi_listener_thread;
+ pthread_t midi_output_thread;
pthread_t message_scheduler_pq_thread;
lpscheduler_t * async_mixer;
lpbuffer_t * lastbuf;
@@ 299,28 306,42 @@ mqd_t astrid_msgq_open(char * qname);
int astrid_msgq_close(mqd_t mqd);
int astrid_msgq_read(mqd_t mqd, lpmsg_t * msg);
-
-/* TODO add POSIX message queues for these too */
-int midi_triggerq_open();
-int midi_triggerq_schedule(int qfd, lpmidievent_t t);
-int midi_triggerq_close(int qfd);
-
+/* TODO evaluate if we really need this. The old use-case was to
+ * store all MIDI values in shared memory as they arrive, so that
+ * they are available to be read in the async instrument callbacks.
+ *
+ * That's still useful (necessary really, for some things) but ought
+ * to be placed into a forthcoming global LMDB session along with all
+ * the other session-scope params.
+ */
int lpmidi_setcc(int device_id, int cc, int value);
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);
+/* TODO notemaps should also probably be moved to LMDB. We want them
+ * to also be global / session-scope so that any event astrid knows
+ * about can be mapped to any valid astrid message.
+ *
+ * In other words, these really aren't MIDI event maps, they should
+ * just be general event-to-message(s) mappings.
+ */
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 lpmidi_get_device_id_by_name(const char * device_name);
+/* TODO same goes for serial values: store them in the LMDB session */
int lpserial_setctl(int device_id, int param_id, size_t value);
int lpserial_getctl(int device_id, int ctl, lpfloat_t * value);
+/* in-progress MIDI APIs */
+int lpmidi_relay_to_instrument(char * instrument_name, unsigned char mtype, unsigned char mid, unsigned char mval);
+int lpmidi_encode_eventbytes(unsigned char buf[3], int channel, unsigned char message_type, int param, int value);
+int lpmidi_encode_msg(lpmsg_t * msg, int channel, unsigned char message_type, int param, int value);
+int lpmidi_get_device_id_by_name(const char * device_name);
+
+/* FIXME there are only JACK ports, now... */
int astrid_get_playback_device_id();
int astrid_get_capture_device_id();
@@ 334,9 355,9 @@ lpbuffer_t * lpsampler_create(char * name, double length_in_seconds, int channel
int lpsampler_destroy(char * name);
int lpsampler_destroy_and_unmap(char * name, lpbuffer_t * buf);
+/* FIXME move get/setid and get/setvalue IPC usages to LMDB */
int lpipc_setid(char * path, int id);
int lpipc_getid(char * path);
-
int lpipc_createvalue(char * path, size_t size);
int lpipc_setvalue(char * path, void * value, size_t size);
int lpipc_getvalue(char * path, void ** value);
M libpippi/src/pippiconstants.h => libpippi/src/pippiconstants.h +1 -1
@@ 68,7 68,7 @@
#define FNV1_32_MAGIC_NUMBER ((u_int32_t)0x811c9dc5)
#define LPMAXNAME 16
-#define LPMAXMSG (PIPE_BUF - (sizeof(double) * 4) - (sizeof(size_t) * 3) - (sizeof(uint16_t) * 2) - LPMAXNAME)
+#define LPMAXMSG (PIPE_BUF - (sizeof(double) * 5) - (sizeof(size_t) * 3) - (sizeof(uint16_t) * 2) - LPMAXNAME)
#define LPMAXPAT 512 - sizeof(size_t)
enum Wavetables {
M libpippi/src/pippitypes.h => libpippi/src/pippitypes.h +9 -10
@@ 40,19 40,18 @@ typedef struct lpmsg_t {
* */
double scheduled;
- /* Timestamp of render completion or trigger away.
+ /* Optional grid quantization values for renders.
*
- * This is assigned either at the point that the message
- * is deserialized along with the buffer and placed onto
- * the mixer queue, or just after a MIDI event or other
- * trigger has been sent away.
+ * `interval` in seconds specifies the grid quantization
+ * as an interval while `offset` sets the phase offset.
*
- * Donno if it is needed, since the message will be
- * destroyed just after it is set, ideally? Or will it be?
- *
- * Possibly better just to log this at the debug level.
+ * A 1 second interval will add a delay to the onset time
+ * for renders so that the event arrives only every N ticks
+ * where N=samplerate * interval, offset by X ticks where
+ * X=samplerate * offset.
* */
- double completed;
+ double interval;
+ double offset;
/* The longest a message in this sequence has taken
* so far to be processed and reach completion.
M pippi/renderer.pxd => pippi/renderer.pxd +15 -14
@@ 85,7 85,8 @@ cdef extern from "astrid.h":
ctypedef struct lpmsg_t:
double initiated
double scheduled
- double completed
+ double interval
+ double offset
double max_processing_time
size_t onset_delay
size_t voice_id
@@ 99,13 100,13 @@ cdef extern from "astrid.h":
double onset
double now
double length
- char type
- char note
- char velocity
- char program
- char bank_msb
- char bank_lsb
- char channel
+ int type
+ int note
+ int velocity
+ int program
+ int bank_msb
+ int bank_lsb
+ int channel
ctypedef struct lpinstrument_t:
const char * name
@@ 144,10 145,6 @@ cdef extern from "astrid.h":
int send_message(char * qname, lpmsg_t msg)
- int midi_triggerq_open()
- int midi_triggerq_schedule(int qfd, lpmidievent_t t)
- int midi_triggerq_close(int qfd)
-
int astrid_msgq_open(char * qname)
int astrid_msgq_read(int mqd, lpmsg_t * msg)
int astrid_msgq_close(int mqd)
@@ 157,6 154,7 @@ cdef extern from "astrid.h":
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 lpmidi_encode_msg(lpmsg_t * msg, int channel, unsigned char message_type, int param, int value)
int lpserial_getctl(int device_id, int ctl, lpfloat_t * value)
@@ 207,7 205,7 @@ cdef class MessageEvent:
cpdef int schedule(MessageEvent self, double now=*)
cdef class MidiEvent:
- cdef lpmidievent_t * event
+ cdef lpmsg_t * msg
cpdef int schedule(MidiEvent self, double now)
cdef class ParamBucket:
@@ 215,7 213,8 @@ cdef class ParamBucket:
cdef str _play_params
cdef class EventTriggerFactory:
- cpdef midi(self, double onset, double length, double freq=*, double amp=*, int note=*, int program=*, int bank_msb=*, int bank_lsb=*, int channel=*, int device=*)
+ cpdef midinote(self, double onset, str instrument_name, double length, double freq=*, double amp=*, int note=*, int velocity=*, int channel=*, int device=*)
+ cpdef midi(self, double onset, str instrument_name, int b1, int b2, int b3, int device=*)
cdef class MidiEventListenerProxy:
cdef int default_device_id
@@ 237,6 236,8 @@ cdef class Instrument:
cdef public dict cache
cdef public lpmsg_t msg # a copy of the last message received
cdef public size_t last_reload
+ cdef public double grid_interval
+ cdef public double grid_offset
cdef public double max_processing_time
cdef public int channels
cdef public double samplerate
M pippi/renderer.pyx => pippi/renderer.pyx +72 -79
@@ 1,14 1,12 @@
#cython: language_level=3
import array
-from cpython cimport array
-from libc.stdlib cimport calloc, free
-from libc.string cimport strcpy, memcpy, strncpy
import logging
from logging.handlers import SysLogHandler
import importlib
import importlib.util
from multiprocessing import Process, SimpleQueue
+import numbers
import os
from pathlib import Path
import platform
@@ 18,6 16,9 @@ import sys
import time
import warnings
+from libc.stdlib cimport calloc, free
+from libc.string cimport strcpy, memcpy, strncpy
+from cpython cimport array
cimport cython
import numpy as np
cimport numpy as np
@@ 25,7 26,7 @@ cimport numpy as np
from pippi import dsp, midi, ugens
from pippi.soundbuffer cimport SoundBuffer
-NUM_COMRADES = 32
+NUM_COMRADES = 8
class InstrumentError(Exception):
pass
@@ 42,7 43,7 @@ if not logger.handlers:
warnings.simplefilter('always')
-cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t msg):
+cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t msg, double grid_interval, double grid_offset):
cdef bytearray strbuf
cdef size_t audiosize, length, msgsize
cdef int channels, samplerate
@@ 54,6 55,12 @@ cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t msg):
samplerate = <int>buf.samplerate
length = <size_t>len(buf)
+ if grid_interval > 0:
+ msg.interval = grid_interval
+
+ if grid_offset > 0:
+ msg.offset = grid_offset
+
# size of audio data
audiosize = length * channels * sizeof(lpfloat_t)
@@ 96,7 103,7 @@ cdef class MessageEvent:
self.msg = <lpmsg_t *>calloc(1, sizeof(lpmsg_t))
self.msg.initiated = 0
self.msg.scheduled = onset
- self.msg.completed = 0
+ #self.msg.completed = 0
self.msg.max_processing_time = max_processing_time
self.msg.onset_delay = 0
#self.msg.voice_id = astrid_get_voice_id()
@@ 117,50 124,38 @@ cdef class MessageEvent:
free(self.msg)
cdef class MidiEvent:
+ # FIXME just use the MessageEvent with an LPMSG_MIDI_TO_DEVICE type
def __cinit__(self,
double onset,
+ str instrument_name,
double length,
- char type,
- char note,
- char velocity,
- char program,
- char bank_msb,
- char bank_lsb,
- char channel
+ int message_type,
+ int note,
+ int velocity,
+ int channel
):
+ instrument_name_byte_string = instrument_name.encode('utf-8')
+ cdef char * byte_instrument_name = instrument_name_byte_string
- self.event = <lpmidievent_t *>calloc(1, sizeof(lpmidievent_t))
- self.event.onset = onset
- self.event.now = 0
- self.event.length = length
- self.event.type = type
- self.event.note = note
- self.event.velocity = velocity
- self.event.program = program
- self.event.bank_msb = bank_msb
- self.event.bank_lsb = bank_lsb
- self.event.channel = channel
-
- cpdef int schedule(MidiEvent self, double now):
- self.event.now = now
- cdef int qfd = midi_triggerq_open()
- if qfd < 0:
- logger.exception('Error opening MIDI fifo q')
- return -1
-
- if midi_triggerq_schedule(qfd, self.event[0]) < 0:
- logger.exception('Error scheduling MidiEvent')
- return -1
+ self.msg = <lpmsg_t *>calloc(1, sizeof(lpmsg_t))
+ self.msg.initiated = 0
+ self.msg.scheduled = onset
+ self.msg.onset_delay = 0
+ self.msg.voice_id = 0
+ self.msg.count = 0
+ self.msg.type = LPMSG_MIDI_TO_DEVICE
+ self.msg.flags = LPFLAG_IS_SCHEDULED if onset > 0 else LPFLAG_NONE
- if midi_triggerq_close(qfd) < 0:
- logger.exception('Error closing MIDI fifo q')
- return -1
+ strcpy(self.msg.instrument_name, byte_instrument_name)
+ lpmidi_encode_msg(self.msg, channel, message_type, note, velocity)
- return 0
+ cpdef int schedule(MidiEvent self, double now):
+ self.msg.initiated = now
+ return send_play_message(self.msg[0])
def __dealloc__(self):
- if self.event is not NULL:
- free(self.event)
+ if self.msg is not NULL:
+ free(self.msg)
cdef class MidiEventListenerProxy:
def __cinit__(self, default_device_id=1):
@@ 205,8 200,8 @@ cdef class EventTriggerFactory:
return params
- cpdef midi(self, double onset, double length, double freq=0, double amp=1, int note=60, int program=1, int bank_msb=0, int bank_lsb=0, int channel=1, int device=-1):
- cdef char _note, velocity
+ cpdef midinote(self, double onset, str instrument_name, double length, double freq=0, double amp=-1, int note=60, int velocity=127, int channel=1, int device=-1):
+ cdef char _note, _velocity
cdef MidiEvent noteon
cdef MidiEvent noteoff
@@ 215,13 210,30 @@ cdef class EventTriggerFactory:
else:
_note = <char>max(0, min(127, note))
- velocity = <char>max(0, min(127, (amp * 127)))
+ if amp >= 0:
+ _velocity = <char>max(0, min(127, (amp * 127)))
+ else:
+ _velocity = <char>max(0, min(127, velocity))
+
+ logger.error('midinote()')
- noteon = MidiEvent(onset, length, <char>NOTE_ON, note, velocity, program, bank_msb, bank_lsb, <char>channel)
- noteoff = MidiEvent(onset + length, 0, <char>NOTE_OFF, note, velocity, program, bank_msb, bank_lsb, <char>channel)
+ noteon = MidiEvent(onset, instrument_name, length, NOTE_ON, _note, _velocity, channel)
+ logger.error(f'{noteon=}')
+ noteoff = MidiEvent(onset + length, instrument_name, 0, NOTE_OFF, _note, _velocity, channel)
+ logger.error(f'{noteoff=}')
return [noteon, noteoff]
+ cpdef midi(self, double onset, str instrument_name, int b1, int b2, int b3, int device=-1):
+ cdef int event_type = NOTE_ON
+ cdef int channel = 0
+
+ return MidiEvent(
+ onset,
+ instrument_name,
+ 0, event_type, b2, b3, channel
+ )
+
def play(self, double onset, str instrument_name, *args, **kwargs):
params = self._parse_params(*args, **kwargs)
return MessageEvent(onset, instrument_name, LPMSG_PLAY, params, 0)
@@ 646,29 658,7 @@ cdef class Instrument:
def reload(self):
logger.debug('Reloading instrument %s from %s' % (self.name, self.path))
- spec = importlib.util.spec_from_file_location(self.name, self.path)
- if spec is not None:
- renderer = importlib.util.module_from_spec(spec)
- try:
- spec.loader.exec_module(renderer)
- except Exception as e:
- logger.exception('Error reloading instrument module: %s' % str(e))
-
- if not hasattr(renderer, '_'):
- renderer._ = None
-
- self.renderer = renderer
- else:
- logger.error('Error reloading instrument. Null spec at path:\n %s' % self.path)
-
- # fill the cache again if there is something to fill:
- if hasattr(self.renderer, 'cache'):
- self.cache = self.renderer.cache()
-
- if hasattr(self.renderer, 'stream'):
- ctx = self.get_event_context()
- self.graph = self.renderer.stream(ctx)
-
+ self.load_renderer(self.name, self.path)
def get_session_int_param(self, u_int32_t hash_key, int default):
logger.debug('Getting INT param from key %d (with default %d)' % (hash_key, default))
@@ 769,10 759,10 @@ cdef int render_event(Instrument instrument, str msgstr):
cdef bint loop
cdef bytes bufstr
cdef bytes render_params = instrument.msg.msg
- cdef int dacid = 0
cdef EventContext ctx
instrument_byte_string = instrument.name.encode('UTF-8')
cdef char * _instrument_ascii_name = instrument_byte_string
+ cdef int grid_interval=0, grid_offset=0
ctx = instrument.get_event_context(msgstr)
@@ 781,6 771,14 @@ cdef int render_event(Instrument instrument, str msgstr):
if hasattr(instrument.renderer, 'before'):
instrument.renderer.before(ctx)
+ if hasattr(instrument.renderer, 'grid'):
+ if isinstance(instrument.renderer.grid, tuple):
+ grid_interval, grid_offset = instrument.renderer.grid
+ elif isinstance(instrument.renderer.grid, numbers.Real):
+ grid_interval = instrument.renderer.GRID
+ elif callable(instrument.renderer.grid):
+ grid_interval, grid_offset = instrument.renderer.grid(ctx)
+
players, loop = collect_players(instrument)
for player in players:
@@ 794,12 792,7 @@ cdef int render_event(Instrument instrument, str msgstr):
if snd is None:
continue
- if isinstance(snd, tuple):
- dacid, snd = snd
- else:
- dacid = 0
-
- bufstr = serialize_buffer(snd, loop, instrument.msg)
+ bufstr = serialize_buffer(snd, loop, instrument.msg, grid_interval, grid_offset)
astrid_instrument_publish_bufstr(_instrument_ascii_name, bufstr, len(bufstr))
except Exception as e:
@@ 851,7 844,7 @@ cdef int trigger_events(Instrument instrument):
ctx = instrument.get_event_context()
- #logger.debug('trigger generation event %s w/params %s' % (str(instrument), trigger_params))
+ logger.error('trigger generation event %s w/params %s' % (str(instrument), trigger_params))
if hasattr(instrument.renderer, 'trigger_before'):
instrument.renderer.trigger_before(ctx)
@@ 872,7 865,7 @@ cdef int trigger_events(Instrument instrument):
return 1
# Schedule the trigger events
- #logger.debug('Scheduling %d trigger events: %s' % (len(trigger_events), trigger_events))
+ logger.error('Scheduling %d trigger events: %s' % (len(trigger_events), trigger_events))
if lpscheduler_get_now_seconds(&now) < 0:
logger.exception('Error getting now seconds during %s trigger scheduling' % ctx.instrument_name)
@@ 884,7 877,7 @@ cdef int trigger_events(Instrument instrument):
continue
if t.schedule(now) < 0:
logger.exception('Error trying to schedule event from %s trigger generation' % ctx.instrument_name)
- #logger.debug('Scheduled event %s' % t)
+ logger.error('Scheduled event %s' % t)
if hasattr(instrument.renderer, 'trigger_done'):
instrument.renderer.trigger_done(ctx)
M setup.py => setup.py +4 -2
@@ 4,7 4,7 @@ from setuptools.extension import Extension
from Cython.Build import cythonize
import numpy as np
-dev = False
+dev = True
INCLUDES = ['libpippi/vendor', 'libpippi/src', '/usr/local/include', np.get_include()]
MACROS = [("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")]
@@ 33,7 33,9 @@ ext_modules = cythonize([
'libpippi/vendor/lmdb/libraries/liblmdb',
'astrid/src'
],
- define_macros=MACROS
+ define_macros=MACROS,
+ extra_compile_args=['-g'],
+ extra_link_args=['-g'],
),
Extension('pippi.microcontrollers', ['pippi/microcontrollers.pyx'],