From d268677564fa1c976146666a6c121215e3b3fe7c Mon Sep 17 00:00:00 2001 From: Erik Schoster Date: Sun, 23 Jun 2024 12:23:23 -0500 Subject: [PATCH] astrid, pippi, libpippi, & instrument updates: astrid: - `sem_close` all the unclosed semaphores! Fixes FD render leaks. - Add `lpsampler_release_and_unmap`, `lpsampler_destroy_and_unmap` to be used for short-lifecycle buffers - Pass a copy of the `lpmsg_t` to `relay_message_to_seq` to avoid race conditions. - Set debug level to LOG_ERR (FIXME: many LOG_INFO messages should be LOG_DEBUG instead. Would also be nice to set log level on the cli) - Add `is_ready` flag to instrument, used for async commands - Use `select()` on `astrid_instrument_tick` with the linenoise async interface to allow for foreground processing in the main thread. also adds `cmdstate` and `cmdbuf` fields to the instrument struct. - Make `init_instrument_message` helper public - Add first pass resampler interface for sampling output from instruments in the same manner as sampling input from the ADC. pippi: - Fix zero division error in `fx.norm` - Fix lpmsg_t param processing in renderer.pyx - Add `ctx.sample`, and `ctx.resample` to python `EventContext` - Add sampler interface wrappers to python `Instrument` - Add a cleanup routine to catch zombie python render processes when the linenoise console yields. libpippi: - Add `lpfilternan()` helper for zeroing pathological floats `littlefield.c` instrument: - Add second gate osc - Split osc bank into odd/even groups - Octave offset has immediate effect `simple.c` instrument: - add trigger callback test loop --- astrid/Makefile | 10 +- astrid/orc/littlefield.c | 146 ++++++++++++++++++++-------- astrid/orc/simple.c | 33 +++++-- astrid/src/astrid.c | 203 +++++++++++++++++++++++++++++++-------- astrid/src/astrid.h | 32 +++++- libpippi/src/pippicore.c | 4 + libpippi/src/pippicore.h | 2 + pippi/fx.pyx | 3 + pippi/renderer.pxd | 24 ++++- pippi/renderer.pyx | 157 ++++++++++++++++++++++++++---- pippi/soundbuffer.pyx | 14 ++- 11 files changed, 508 insertions(+), 120 deletions(-) diff --git a/astrid/Makefile b/astrid/Makefile index 609009e..ea4b7dc 100644 --- a/astrid/Makefile +++ b/astrid/Makefile @@ -106,8 +106,8 @@ littlefield: mkdir -p build echo "Building littlefield..."; - $(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/getmidideviceid.c $(LPLIBS) -o build/getmidideviceid - $(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/hashtest.c $(LPLIBS) -o build/hashtest + #$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/getmidideviceid.c $(LPLIBS) -o build/getmidideviceid + #$(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c src/hashtest.c $(LPLIBS) -o build/hashtest #$(CC) $(LPFLAGS) -fsanitize=address -fsanitize=leak -fno-omit-frame-pointer $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/littlefield.c $(LPLIBS) -o build/littlefield $(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/littlefield.c $(LPLIBS) -o build/littlefield @@ -121,11 +121,11 @@ astrid-pulsar: $(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/pulsar.c $(LPLIBS) -o build/astrid-pulsar -astrid-simple: +simple: mkdir -p build - $(CC) $(LPFLAGS) -fsanitize=address -fsanitize=leak -fno-omit-frame-pointer $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/simple.c $(LPLIBS) -o build/astrid-simple - #$(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/simple-san + $(CC) $(LPFLAGS) $(LPINCLUDES) $(LPSOURCES) src/astrid.c orc/simple.c $(LPLIBS) -o build/simple build: clean astrid-q astrid-serial-tools astrid-ipc astrid-devices astrid-midimap astrid-pulsar astrid-simple diff --git a/astrid/orc/littlefield.c b/astrid/orc/littlefield.c index 46dc29f..9621dc7 100644 --- a/astrid/orc/littlefield.c +++ b/astrid/orc/littlefield.c @@ -5,6 +5,7 @@ #define SR 48000 #define CHANNELS 2 #define ADC_LENGTH 30 +#define RESAMPLER_LENGTH 30 #define MIC_ATTENUATION 0.2f #define NUMOSCS 10 @@ -60,10 +61,14 @@ enum InstrumentParams { PARAM_OSC_DRIFT_SPEED, PARAM_OSC_OCTAVE_SPREAD, PARAM_OSC_OCTAVE_OFFSET, + PARAM_OSC_FREQ_UPDATE_ON_ENV_RESET, PARAM_OSC_FREQS, - PARAM_GATE_AMP, - PARAM_GATE_SPEED, + PARAM_GATE1_AMP, + PARAM_GATE2_AMP, + PARAM_GATE1_SPEED, + PARAM_GATE2_SPEED, + PARAM_GATE_SPEED_MULTIPLIER, PARAM_GATE_PULSEWIDTH, PARAM_GATE_SATURATION, PARAM_GATE_SHAPE, @@ -113,7 +118,8 @@ typedef struct localctx_t { lpbuffer_t * limit1buf; lpbuffer_t * limit2buf; - lppulsarosc_t * gate; + lppulsarosc_t * gate1; + lppulsarosc_t * gate2; lpshapeosc_t * gate_drifter; lpbuffer_t * gate_drift_win; @@ -123,7 +129,8 @@ typedef struct localctx_t { lpfloat_t env_phases[NUMOSCS]; lpfloat_t env_phaseincs[NUMOSCS]; - lpfloat_t octave_mults[NUMOSCS]; + lpfloat_t octave_offsets[NUMOSCS]; + lpfloat_t octave_spreads[NUMOSCS]; } localctx_t; lpfloat_t osc_mix(lpfloat_t pos, int chan) { @@ -179,6 +186,9 @@ int param_map_callback(void * arg, char * keystr, char * valstr) { } else if(strcmp(keystr, "octoffset") == 0) { extract_int32_from_token(valstr, &val_i32); astrid_instrument_set_param_int32(instrument, PARAM_OSC_OCTAVE_OFFSET, val_i32); + } else if(strcmp(keystr, "freqsnap") == 0) { + extract_int32_from_token(valstr, &val_i32); + astrid_instrument_set_param_int32(instrument, PARAM_OSC_FREQ_UPDATE_ON_ENV_RESET, val_i32); } else if(strcmp(keystr, "freqs") == 0) { num_freqs = extract_floatlist_from_token(valstr, val_floatlist, MAXNUMFREQS); astrid_instrument_set_param_float_list(instrument, PARAM_OSC_FREQS, val_floatlist, num_freqs); @@ -189,12 +199,22 @@ int param_map_callback(void * arg, char * keystr, char * valstr) { extract_float_from_token(valstr, &val_f); astrid_instrument_set_param_float(instrument, PARAM_OSC_TO_OUT2, val_f); - } else if(strcmp(keystr, "gamp") == 0) { + } else if(strcmp(keystr, "g1amp") == 0) { + extract_float_from_token(valstr, &val_f); + astrid_instrument_set_param_float(instrument, PARAM_GATE1_AMP, val_f); + } else if(strcmp(keystr, "g1speed") == 0) { + extract_float_from_token(valstr, &val_f); + astrid_instrument_set_param_float(instrument, PARAM_GATE2_SPEED, val_f); + } else if(strcmp(keystr, "g2amp") == 0) { extract_float_from_token(valstr, &val_f); - astrid_instrument_set_param_float(instrument, PARAM_GATE_AMP, val_f); - } else if(strcmp(keystr, "gspeed") == 0) { + astrid_instrument_set_param_float(instrument, PARAM_GATE2_AMP, val_f); + } else if(strcmp(keystr, "g2speed") == 0) { extract_float_from_token(valstr, &val_f); - astrid_instrument_set_param_float(instrument, PARAM_GATE_SPEED, val_f); + astrid_instrument_set_param_float(instrument, PARAM_GATE2_SPEED, val_f); + + } else if(strcmp(keystr, "gmult") == 0) { + extract_float_from_token(valstr, &val_f); + astrid_instrument_set_param_float(instrument, PARAM_GATE_SPEED_MULTIPLIER, val_f); } else if(strcmp(keystr, "gpw") == 0) { extract_float_from_token(valstr, &val_f); astrid_instrument_set_param_float(instrument, PARAM_GATE_PULSEWIDTH, val_f); @@ -301,18 +321,20 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg size_t i; int j; lpfloat_t freqs[MAXNUMFREQS]; - lpfloat_t sample, osc_amp, env, in1, in2, d1, d2, p, last_p=-1, + lpfloat_t osc_sample, osc_sample_group1, osc_sample_group2, + osc_amp, env1, env2, in1, in2, d1, d2, p, last_p=-1, distortion_amount, distortion_mix, osc_pw, osc_saturation, osc_env_speed, osc_drift_amount, drift, osc_drift_speed, osc_out1_mix, osc_out2_mix, gate_out1_mix, gate_out2_mix, - gated_sample, gate_amp, octave_mult, amp_mod, + gate1_sample, gate2_sample, gate1_amp, gate2_amp, amp_mod, in1_out1_mix, in1_out2_mix, in2_out1_mix, in2_out2_mix, - gate_shape, gate_speed, gate_pw, gate_drift_amount, gate_drift_speed, + gate_shape, gate1_speed, gate2_speed, gate_speed_multiplier, + gate_pw, gate_drift_amount, gate_drift_speed, gate_drift_stability, gate_drift_density, gate_drift_periodicity, gate_drift, gate_saturation, tracked_freq=220.f; int32_t octave_spread, octave_offset, gate_reset, gate_repeat, - pitch_tracking_is_enabled, distortion_type; + freq_snap, pitch_tracking_is_enabled, distortion_type; lppatternbuf_t gate_pattern; lpinstrument_t * instrument = (lpinstrument_t *)arg; localctx_t * ctx = (localctx_t *)instrument->context; @@ -334,16 +356,23 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg osc_drift_speed = astrid_instrument_get_param_float(instrument, PARAM_OSC_DRIFT_SPEED, 0.5f); octave_spread = astrid_instrument_get_param_int32(instrument, PARAM_OSC_OCTAVE_SPREAD, 6); octave_offset = astrid_instrument_get_param_int32(instrument, PARAM_OSC_OCTAVE_OFFSET, -2); + freq_snap = astrid_instrument_get_param_int32(instrument, PARAM_OSC_FREQ_UPDATE_ON_ENV_RESET, 0); astrid_instrument_get_param_float_list(instrument, PARAM_OSC_FREQS, num_freqs, freqs); - gate_amp = astrid_instrument_get_param_float(instrument, PARAM_GATE_AMP, 0.f); - gate_amp = LPFX.lpf1(gate_amp, &ctx->gateampsmooth, 1000.f, SR); + gate1_amp = astrid_instrument_get_param_float(instrument, PARAM_GATE1_AMP, 0.f); + gate1_amp = LPFX.lpf1(gate1_amp, &ctx->gateampsmooth, 1000.f, SR); + + gate2_amp = astrid_instrument_get_param_float(instrument, PARAM_GATE2_AMP, 0.f); + gate2_amp = LPFX.lpf1(gate2_amp, &ctx->gateampsmooth, 1000.f, SR); + gate_out1_mix = astrid_instrument_get_param_float(instrument, PARAM_GATE_TO_OUT1, 0.5f); gate_out2_mix = astrid_instrument_get_param_float(instrument, PARAM_GATE_TO_OUT2, 0.5f); gate_reset = astrid_instrument_get_param_int32(instrument, PARAM_GATE_PHASE_RESET, 0); gate_repeat = astrid_instrument_get_param_int32(instrument, PARAM_GATE_REPEAT, 1); gate_shape = astrid_instrument_get_param_float(instrument, PARAM_GATE_SHAPE, 0.f); - gate_speed = astrid_instrument_get_param_float(instrument, PARAM_GATE_SPEED, 2.f); + gate1_speed = astrid_instrument_get_param_float(instrument, PARAM_GATE1_SPEED, 2.f); + gate2_speed = astrid_instrument_get_param_float(instrument, PARAM_GATE2_SPEED, 2.f); + gate_speed_multiplier = astrid_instrument_get_param_float(instrument, PARAM_GATE_SPEED_MULTIPLIER, 1.f); gate_drift_amount = astrid_instrument_get_param_float(instrument, PARAM_GATE_DRIFT_DEPTH, 0.f); gate_drift_speed = astrid_instrument_get_param_float(instrument, PARAM_GATE_DRIFT_SPEED, 1.f) * 100.f; gate_drift_stability = astrid_instrument_get_param_float(instrument, PARAM_GATE_DRIFT_STABILITY, 0.1f) * 0.5f; @@ -369,20 +398,29 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg ctx->gate_drifter->density = gate_drift_density; ctx->gate_drifter->periodicity = gate_drift_periodicity; gate_drift = LPShapeOsc.process(ctx->gate_drifter) * gate_drift_amount; - ctx->gate->window_morph = fmin(gate_shape, 0.999f); // TODO investigate overflows - ctx->gate->freq = gate_speed + gate_drift; - ctx->gate->pulsewidth = gate_pw; - ctx->gate->saturation = gate_saturation; - LPPulsarOsc.burst_bytes(ctx->gate, gate_pattern.pattern, gate_pattern.length); - ctx->gate->once = !gate_repeat; + + ctx->gate1->window_morph = fmin(gate_shape, 0.999f); + ctx->gate1->freq = (gate1_speed * gate_speed_multiplier) + gate_drift; + ctx->gate1->pulsewidth = gate_pw; + ctx->gate1->saturation = gate_saturation; + LPPulsarOsc.burst_bytes(ctx->gate1, gate_pattern.pattern, gate_pattern.length); + ctx->gate1->once = !gate_repeat; + + ctx->gate2->window_morph = fmin(gate_shape, 0.999f); + ctx->gate2->freq = (gate2_speed * gate_speed_multiplier) + gate_drift; + ctx->gate2->pulsewidth = gate_pw; + ctx->gate2->saturation = gate_saturation; + LPPulsarOsc.burst_bytes(ctx->gate2, gate_pattern.pattern, gate_pattern.length); + ctx->gate2->once = !gate_repeat; if(gate_reset) { - ctx->gate->phase = 0.f; + ctx->gate1->phase = 0.f; + ctx->gate2->phase = 0.f; astrid_instrument_set_param_int32(instrument, PARAM_GATE_PHASE_RESET, 0); } for(i=0; i < blocksize; i++) { - sample = d1 = d2 = 0.f; + osc_sample_group1 = osc_sample_group2 = d1 = d2 = 0.f; amp_mod = LPEnvelopeFollower.process(ctx->envf, input[0][i]); amp_mod = LPFX.lpf1(amp_mod, &ctx->ampmodsmooth, 100.f, SR); @@ -409,22 +447,30 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg drift = LPInterpolation.linear_pos(ctx->drifters[j], ctx->drifters[j]->phase) * osc_drift_amount; // set the freq to the freqs table pitch or tracked pitch if tracking is enabled + if(!freq_snap) { + ctx->octave_offsets[j] = pow(2, octave_offset); + } + ctx->oscs[j]->freq = freqs[j % num_freqs]; if(pitch_tracking_is_enabled) ctx->oscs[j]->freq = tracked_freq; - ctx->oscs[j]->freq *= ctx->octave_mults[j] + drift; + ctx->oscs[j]->freq *= (ctx->octave_offsets[j] * ctx->octave_spreads[j]) + drift; // mix in the enveloped osc voice - sample += LPPulsarOsc.process(ctx->oscs[j]) * LPInterpolation.linear_pos(ctx->env, ctx->env_phases[j]) * ((1.f/NUMOSCS)*3.f); + if(j % 2 == 0) { + osc_sample_group1 += LPPulsarOsc.process(ctx->oscs[j]) * LPInterpolation.linear_pos(ctx->env, ctx->env_phases[j]) * ((1.f/NUMOSCS)*3.f); + } else { + osc_sample_group2 += LPPulsarOsc.process(ctx->oscs[j]) * LPInterpolation.linear_pos(ctx->env, ctx->env_phases[j]) * ((1.f/NUMOSCS)*3.f); + } // advance the voice envelope phase ctx->env_phases[j] += ctx->env_phaseincs[j] * (1.f+osc_env_speed) * 3.f; // on voice envelope phase resets, recalculate the octave multiplier if(ctx->env_phases[j] >= 1.f) { - octave_mult = pow(2, octave_offset); octave_spread = LPRand.randint(-octave_spread, octave_spread); - octave_mult *= pow(2, octave_spread); - ctx->octave_mults[j] = octave_mult; + ctx->octave_spreads[j] = pow(2, octave_spread); + ctx->octave_offsets[j] = pow(2, octave_offset); + /* syslog(LOG_ERR, "GDRIFT=%f GDRIFTSPEED=%f GDRIFTAMOUNT=%f\n", gate_drift, gate_drift_speed, gate_drift_amount); @@ -440,19 +486,23 @@ int audio_callback(size_t blocksize, float ** input, float ** output, void * arg } // gated output - env = LPPulsarOsc.process(ctx->gate); - gated_sample = sample * env * gate_amp; + env1 = LPPulsarOsc.process(ctx->gate1); + gate1_sample = osc_sample_group1 * env1 * gate1_amp; + + env2 = LPPulsarOsc.process(ctx->gate2); + gate2_sample = osc_sample_group2 * env2 * gate2_amp; // osc output - sample *= osc_amp * amp_mod; + osc_sample = osc_sample_group1 + osc_sample_group2; + osc_sample *= osc_amp * amp_mod; // mic feedback in1 = input[0][i] * MIC_ATTENUATION; in2 = input[1][i] * MIC_ATTENUATION; // mix everything - output[0][i] += (sample * osc_out1_mix) + (gated_sample * gate_out1_mix) + (in1 * in1_out1_mix) + (in2 * in2_out1_mix); - output[1][i] += (sample * osc_out2_mix) + (gated_sample * gate_out2_mix) + (in2 * in1_out2_mix) + (in2 * in2_out2_mix); + output[0][i] += (osc_sample * osc_out1_mix) + ((gate1_sample + gate2_sample) * gate_out1_mix) + (in1 * in1_out1_mix) + (in2 * in2_out1_mix); + output[1][i] += (osc_sample * osc_out2_mix) + ((gate1_sample + gate2_sample) * gate_out2_mix) + (in2 * in1_out2_mix) + (in2 * in2_out2_mix); // apply distortion if(distortion_amount > 0) { @@ -508,7 +558,8 @@ int main() { ctx->env_phaseincs[i] = (1.f/SR) * LPRand.rand(0.005f, 0.03f) * 0.5f; ctx->drifters[i] = LPWavetable.create(WT_RND, 4096); ctx->drift_phaseincs[i] = (1.f/SR) * LPRand.rand(0.005f, 0.03f); - ctx->octave_mults[i] = 1.f; + ctx->octave_offsets[i] = 1.f; + ctx->octave_spreads[i] = 1.f; ctx->oscs[i] = LPPulsarOsc.create(4, 2, WT_SINE, WTSIZE, WT_TRI2, WTSIZE, @@ -521,7 +572,14 @@ int main() { ctx->oscs[i]->phase = 0.f; } - ctx->gate = LPPulsarOsc.create(0, 4, + ctx->gate1 = LPPulsarOsc.create(0, 4, + WIN_SINEOUT, WTSIZE, + WIN_SINE, WTSIZE, + WIN_PHASOR, WTSIZE, + WIN_SINEOUT, WTSIZE + ); + + ctx->gate2 = LPPulsarOsc.create(0, 4, WIN_SINEOUT, WTSIZE, WIN_SINE, WTSIZE, WIN_PHASOR, WTSIZE, @@ -533,16 +591,21 @@ int main() { ctx->envf = LPEnvelopeFollower.create(12.f, SR); - ctx->gate->samplerate = (lpfloat_t)SR; - ctx->gate->window_morph_freq = 0; - ctx->gate->freq = 30.f; - ctx->gate->phase = 0.f; + ctx->gate1->samplerate = (lpfloat_t)SR; + ctx->gate1->window_morph_freq = 0; + ctx->gate1->freq = 30.f; + ctx->gate1->phase = 0.f; + + ctx->gate2->samplerate = (lpfloat_t)SR; + ctx->gate2->window_morph_freq = 0; + ctx->gate2->freq = 30.f; + ctx->gate2->phase = 0.f; ctx->gate_drift_win = LPWavetable.create(WT_SINE, 4096); ctx->gate_drifter = LPShapeOsc.create(ctx->gate_drift_win); // Set the callbacks for streaming, async renders and param updates - if((instrument = astrid_instrument_start(NAME, CHANNELS, 0, ADC_LENGTH, (void*)ctx, + if((instrument = astrid_instrument_start(NAME, CHANNELS, 0, ADC_LENGTH, RESAMPLER_LENGTH, (void*)ctx, NULL, NULL, audio_callback, renderer_callback, param_map_callback, NULL)) == NULL) { fprintf(stderr, "Could not start instrument: (%d) %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); @@ -568,7 +631,8 @@ int main() { LPBuffer.destroy(ctx->drifters[o]); } - LPPulsarOsc.destroy(ctx->gate); + LPPulsarOsc.destroy(ctx->gate1); + LPPulsarOsc.destroy(ctx->gate2); LPShapeOsc.destroy(ctx->gate_drifter); LPBuffer.destroy(ctx->gate_drift_win); LPBuffer.destroy(ctx->env); diff --git a/astrid/orc/simple.c b/astrid/orc/simple.c index c024abb..281bdc6 100644 --- a/astrid/orc/simple.c +++ b/astrid/orc/simple.c @@ -7,8 +7,24 @@ #define ADC_LENGTH 30 int trigger_callback(void * arg) { + lpmsg_t msg = {0}; + double now = 0; lpinstrument_t * instrument = (lpinstrument_t *)arg; - syslog(LOG_INFO, "INSTRUMENT TRIGGER CALLBACK %s\n", instrument->name); + syslog(LOG_ERR, "INSTRUMENT TRIGGER CALLBACK %s\n", instrument->name); + + strncpy(msg.instrument_name, "simplesea", LPMAXNAME-1); + + if(lpscheduler_get_now_seconds(&now) < 0) return -1; + msg.initiated = now; + + msg.type = LPMSG_PLAY; + send_message(instrument->qname, msg); + + msg.type = LPMSG_TRIGGER; + msg.scheduled = 0.02f; + msg.flags = LPFLAG_IS_SCHEDULED; + send_message(instrument->qname, msg); + return 0; } @@ -17,14 +33,16 @@ int renderer_callback(void * arg) { lpbuffer_t * out; lpinstrument_t * instrument = (lpinstrument_t *)arg; - out = LPBuffer.create(SR, instrument->channels, SR); + size_t l = SR * 0.1; + + out = LPBuffer.create(l, instrument->channels, SR); syslog(LOG_INFO, "INSTRUMENT CALLBACK reading from ringbuffer\n"); - if(lpsampler_read_ringbuffer_block(instrument->adcname, instrument->adcbuf, 0, out) < 0) { + if(lpsampler_read_ringbuffer_block(instrument->adcname, instrument->adcbuf, l*2, out) < 0) { return -1; } - syslog(LOG_INFO, "INSTRUMENT CALLBACK filled buffer with value 10 %f\n", out->data[10]); - LPFX.norm(out, 1); + syslog(LOG_ERR, "INSTRUMENT CALLBACK filled buffer with value 10 %f\n", out->data[10]); + LPFX.norm(out, 0.01); if(send_render_to_mixer(instrument, out) < 0) { return -1; @@ -39,7 +57,10 @@ int main() { lpinstrument_t * instrument; // Set the callbacks for streaming, async renders and param updates - if((instrument = astrid_instrument_start(NAME, CHANNELS, 0, ADC_LENGTH, NULL, NULL, renderer_callback, NULL, trigger_callback)) == NULL) { + if((instrument = astrid_instrument_start(NAME, CHANNELS, 0, + ADC_LENGTH, ADC_LENGTH, + NULL, NULL, NULL, NULL, + renderer_callback, NULL, trigger_callback)) == NULL) { fprintf(stderr, "Could not start instrument: (%d) %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } diff --git a/astrid/src/astrid.c b/astrid/src/astrid.c index b800018..9f98c3a 100644 --- a/astrid/src/astrid.c +++ b/astrid/src/astrid.c @@ -1,7 +1,5 @@ #include "astrid.h" -int init_instrument_message(lpmsg_t * msg, char * instrument_name); - static volatile int * astrid_instrument_is_running; static pthread_mutex_t astrid_nursery_lock = PTHREAD_MUTEX_INITIALIZER; @@ -357,13 +355,14 @@ int lpipc_destroyid(char * path) { int lpipc_createvalue(char * path, size_t size) { int shmfd; + sem_t * sem; char * semname; /* Construct the sempahore name by stripping the /tmp prefix */ semname = path + 4; /* Create the POSIX semaphore and initialize it to 1 */ - if(sem_open(semname, O_CREAT | O_EXCL, LPIPC_PERMS, 1) == NULL) { + if((sem = sem_open(semname, O_CREAT | O_EXCL, LPIPC_PERMS, 1)) == NULL) { syslog(LOG_ERR, "lpipc_createvalue failed to create semaphore %s. Error: %s\n", semname, strerror(errno)); return -1; } @@ -379,6 +378,11 @@ int lpipc_createvalue(char * path, size_t size) { return -1; } + if(sem_close(sem) < 0) { + syslog(LOG_ERR, "lpipc_createvalue sem_close Could not close semaphore\n"); + return -1; + } + close(shmfd); return 0; @@ -624,6 +628,11 @@ lpbuffer_t * lpsampler_create(char * name, double length_in_seconds, int channel buf->boundry = length-1; buf->range = length; + if(sem_close(sem) < 0) { + syslog(LOG_ERR, "lpsampler_create sem_close Could not close semaphore\n"); + return NULL; + } + close(shmfd); return buf; @@ -731,15 +740,64 @@ int lpsampler_release(char * name) { return 0; } -int lpsampler_destroy(char * name) { +int lpsampler_release_and_unmap(char * name, lpbuffer_t * buf) { + sem_t * sem; + char path[PATH_MAX] = {0}; + lpsampler_get_path(name, path); + + munmap(buf, buf->length * sizeof(lpfloat_t) + sizeof(lpbuffer_t)); + + /* Open the semaphore */ + if((sem = sem_open(path, 0)) == SEM_FAILED) { + syslog(LOG_ERR, "lpipc_buffer_release failed to open semaphore %s. Error: %s\n", path, strerror(errno)); + return -1; + } + + /* Release the lock on the semaphore */ + if(sem_post(sem) < 0) { + syslog(LOG_ERR, "lpipc_buffer_release failed to unlock %s. Error: %s\n", path, strerror(errno)); + return -1; + } + + /* Clean up sempahore resources */ + if(sem_close(sem) < 0) { + syslog(LOG_ERR, "lpipc_buffer_release sem_close Could not close semaphore\n"); + return -1; + } + + return 0; +} + +int lpsampler_destroy_and_unmap(char * name, lpbuffer_t * buf) { char path[PATH_MAX] = {0}; lpsampler_get_path(name, path); + /* Unmap the shared memory */ + munmap(buf, buf->length * sizeof(lpfloat_t) + sizeof(lpbuffer_t)); + /* Unlink the shared memory buffer */ if(shm_unlink(path) < 0) { syslog(LOG_ERR, "lpsampler_destroy shm_unlink. Error: %s\n", strerror(errno)); return -1; + } + /* Unlink the sempahore */ + if(sem_unlink(path) < 0) { + syslog(LOG_ERR, "lpsampler_destroy sem_unlink Could not destroy semaphore\n"); + return -1; + } + + return 0; +} + +int lpsampler_destroy(char * name) { + char path[PATH_MAX] = {0}; + lpsampler_get_path(name, path); + + /* Unlink the shared memory buffer */ + if(shm_unlink(path) < 0) { + syslog(LOG_ERR, "lpsampler_destroy shm_unlink. Error: %s\n", strerror(errno)); + return -1; } /* Unlink the sempahore */ @@ -782,7 +840,7 @@ int lpsampler_write_ringbuffer_block( for(i=0; i < blocksize_in_frames; i++) { insert_pos = ((buf->pos+i) * channels + c) % boundry; sample = *channelp++; - buf->data[insert_pos] = sample; + buf->data[insert_pos] = lpfilternan(sample); } } @@ -816,11 +874,11 @@ int lpsampler_read_ringbuffer_block( syslog(LOG_INFO, "READ RINGBUF: aquiring lock on %s\n", name); /* Aquire a lock on the buffer */ if(lpsampler_aquire(name) < 0) { - syslog(LOG_ERR, "Could not aquire ADC buffer shm for update\n"); + syslog(LOG_ERR, "Could not aquire %s ring buffer shm for update\n", name); return -1; } - syslog(LOG_INFO, "READ RINGBUF: adc buffer has value 10 %f\n", buf->data[10]); + syslog(LOG_INFO, "READ RINGBUF: %s ring buffer has value 10 %f\n", name, buf->data[10]); /* * buf->pos is the last frame written to the circular buffer @@ -832,7 +890,7 @@ int lpsampler_read_ringbuffer_block( for(i=0; i < out->length; i++) { bufidx = (start + i) % buf->length; for(c=0; c < buf->channels; c++) { - out->data[i * buf->channels + c] = buf->data[bufidx * buf->channels + c]; + out->data[i * buf->channels + c] = lpfilternan(buf->data[bufidx * buf->channels + c]); } } @@ -1309,6 +1367,11 @@ ssize_t lpcounter_create(char * name) { // Initialize the shared memory with 0 memcpy(shmaddr, &counter_val, sizeof(size_t)); + if(sem_close(sem) < 0) { + syslog(LOG_ERR, "lpcounter_create sem_close Could not close semaphore\n"); + return -1; + } + munmap(shmaddr, sizeof(size_t)); close(shmfd); @@ -1550,6 +1613,11 @@ lpbuffer_t * deserialize_buffer(char * buffer_code, lpmsg_t * msg) { return NULL; } + if(sem_close(sem) < 0) { + syslog(LOG_ERR, "deserialize_buffer sem_close Could not close semaphore\n"); + return NULL; + } + close(fd); return buf; @@ -2438,7 +2506,7 @@ static inline void scheduler_mix_buffers(lpscheduler_t * s) { sample += current->buf->data[current->pos * current->buf->channels + bufc]; } - s->current_frame[c] = sample; + s->current_frame[c] = lpfilternan(sample); } } @@ -2673,14 +2741,18 @@ int astrid_instrument_jack_callback(jack_nframes_t nframes, void * arg) { } } -#if 0 /* clamp output */ for(c=0; c < instrument->channels; c++) { for(i=0; i < (size_t)nframes; i++) { output_channels[c][i] = fmax(-1.f, fmin(output_channels[c][i], 1.f)); } } -#endif + + /* write the output block into the resampler ringbuffer */ + if(lpsampler_write_ringbuffer_block(instrument->resamplername, instrument->resamplerbuf, output_channels, instrument->channels, nframes) < 0) { + syslog(LOG_ERR, "Error writing into adc ringbuf\n"); + return 0; + } return 0; } @@ -2793,7 +2865,7 @@ void * instrument_seq_pq(void * arg) { return 0; } -int relay_message_to_seq(lpinstrument_t * instrument) { +int relay_message_to_seq(lpinstrument_t * instrument, lpmsg_t msg) { lpmsgpq_node_t * d; double seq_delay, now=0; @@ -2801,7 +2873,7 @@ int relay_message_to_seq(lpinstrument_t * instrument) { syslog(LOG_DEBUG, "SEQ PQ MSG: pqnode_index=%d\n", instrument->pqnode_index); d = &instrument->pqnodes[instrument->pqnode_index]; - memcpy(&d->msg, &instrument->msg, sizeof(lpmsg_t)); + memcpy(&d->msg, &msg, sizeof(lpmsg_t)); instrument->pqnode_index += 1; while(instrument->pqnode_index >= NUM_NODES) instrument->pqnode_index -= NUM_NODES; @@ -2812,8 +2884,11 @@ int relay_message_to_seq(lpinstrument_t * instrument) { /* Hold on to the message as long as possible while still * trying to leave some time for processing before the target deadline */ - seq_delay = instrument->msg.scheduled - (instrument->msg.max_processing_time * 2); - d->timestamp = instrument->msg.initiated + seq_delay; + seq_delay = msg.scheduled - (msg.max_processing_time * 2); + d->timestamp = msg.initiated + seq_delay; + + syslog(LOG_ERR, "d->timestamp=%f msg.scheduled=%f msg.initiated=%f\n", + d->timestamp, msg.scheduled, msg.initiated); // Remove the scheduled flag before relaying the message d->msg.flags &= ~LPFLAG_IS_SCHEDULED; @@ -2859,12 +2934,12 @@ 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_ERR, "C MSG: name=%s\n", instrument->msg.instrument_name); + syslog(LOG_ERR, "C MSG: scheduled=%f\n", instrument->msg.scheduled); + syslog(LOG_ERR, "C MSG: voice_id=%d\n", (int)instrument->msg.voice_id); + syslog(LOG_ERR, "C MSG: type=%d\n", (int)instrument->msg.type); + syslog(LOG_ERR, "C MSG: flags=%d\n", (int)instrument->msg.flags); + syslog(LOG_ERR, "C MSG: is_scheduled=%d\n", is_scheduled); // Handle shutdown early if(instrument->msg.type == LPMSG_SHUTDOWN) { @@ -2872,7 +2947,7 @@ void * instrument_message_thread(void * arg) { instrument->is_running = 0; // send the shutdown message to the seq thread and external relay - if(relay_message_to_seq(instrument) < 0) { + if(relay_message_to_seq(instrument, instrument->msg) < 0) { syslog(LOG_ERR, "%s renderer: Could not read relay message to seq. Error: (%d) %s\n", instrument->name, errno, strerror(errno)); } if(instrument->ext_relay_enabled) { @@ -2891,7 +2966,7 @@ void * instrument_message_thread(void * arg) { if(is_scheduled) { // Scheduled messages get sent to the sequencer for handling later syslog(LOG_ERR, "C IS SCHEDULED msg.scheduled %f\n", instrument->msg.scheduled); - if(relay_message_to_seq(instrument) < 0) { + if(relay_message_to_seq(instrument, instrument->msg) < 0) { syslog(LOG_ERR, "%s renderer: Could not read relay message to seq. Error: (%d) %s\n", instrument->name, errno, strerror(errno)); } continue; @@ -3357,6 +3432,7 @@ lpinstrument_t * astrid_instrument_start( int channels, int ext_relay_enabled, double adc_length, + double resampler_length, void * ctx, char * tty, char * midi_device_name, @@ -3377,6 +3453,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)); openlog(name, LOG_PID, LOG_USER); syslog(LOG_DEBUG, "starting %s instrument...\n", name); @@ -3389,6 +3466,7 @@ lpinstrument_t * astrid_instrument_start( instrument->update = update; instrument->trigger = trigger; instrument->ext_relay_enabled = ext_relay_enabled; + instrument->is_ready = 1; instrument->midi_device_id = -1; if(midi_device_name != NULL) { @@ -3474,6 +3552,13 @@ lpinstrument_t * astrid_instrument_start( 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->channels, instrument->samplerate)) == NULL) { + syslog(LOG_INFO, "Could not create instrument resampler buffer\n"); + goto astrid_instrument_shutdown_with_error; + } + /* init scheduler */ instrument->async_mixer = scheduler_create(1, instrument->channels, instrument->samplerate); @@ -3697,6 +3782,13 @@ int astrid_instrument_stop(lpinstrument_t * instrument) { return -1; } + syslog(LOG_DEBUG, "Cleaning up resampler ringbuf...\n"); + if(lpsampler_destroy(instrument->resamplername) < 0) { + syslog(LOG_ERR, "Error while removing resampler ringbuf, dang! Other cleanup is done tho.\n"); + closelog(); + return -1; + } + /* cleanup the pq memory */ pqueue_free(instrument->msgpq); free(instrument->pqnodes); @@ -3857,6 +3949,7 @@ int send_render_to_mixer(lpinstrument_t * instrument, lpbuffer_t * buf) { int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * bufstr, size_t size) { int shmfd; void * shmaddr; + sem_t * sem; char buffer_code[LPKEY_MAXLENGTH] = {0}; ssize_t buffer_id = 0; lpmsg_t msg = {0}; @@ -3874,7 +3967,7 @@ int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * buf // write the bufstr to shared memory at the key location /* Create the POSIX semaphore and initialize it to 1 */ - if(sem_open(buffer_code, O_CREAT | O_EXCL, LPIPC_PERMS, 1) == NULL) { + if((sem = sem_open(buffer_code, O_CREAT | O_EXCL, LPIPC_PERMS, 1)) == NULL) { syslog(LOG_ERR, "publish_bufstr: failed to create semaphore %s. Error: %s\n", buffer_code, strerror(errno)); return -1; } @@ -3911,38 +4004,72 @@ int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * buf // unmap the memory... munmap(shmaddr, size); + if(sem_close(sem) < 0) { + syslog(LOG_ERR, "publish_bufstr sem_close Could not close semaphore\n"); + return -1; + } + close(shmfd); return 0; } -int astrid_instrument_tick(lpinstrument_t * instrument) { +int astrid_instrument_process_command_tick(lpinstrument_t * instrument) { char * cmdline; size_t cmdlength; - if(instrument->is_running == 0) return 0; + cmdline = linenoiseEditFeed(&instrument->cmdstate); - cmdline = linenoise("^_- "); + if(cmdline != linenoiseEditMore) { + linenoiseEditStop(&instrument->cmdstate); - if(cmdline == NULL) { - return 0; + /* add the command to the history */ + linenoiseHistoryAdd(cmdline); + linenoiseHistorySave("history.txt"); // FIXME put this in .local or etc + + cmdlength = strnlen(cmdline, ASTRID_MAX_CMDLINE); + + printf("processing msg: %s (%ld)\n", cmdline, cmdlength); + fflush(stdout); + + if(parse_message_from_cmdline(cmdline, cmdlength, &instrument->cmd) < 0) { + syslog(LOG_ERR, "Could not parse message from cmdline %s\n", cmdline); + return -1; + } + + free(cmdline); + instrument->is_ready = 1; } - /* add the command to the history */ - linenoiseHistoryAdd(cmdline); + return 0; +} + +int astrid_instrument_tick(lpinstrument_t * instrument) { + struct timeval timeout; + fd_set readfds; + int ret; + + if(instrument->is_running == 0) return 0; - cmdlength = strnlen(cmdline, ASTRID_MAX_CMDLINE); + if(instrument->is_ready) { + linenoiseEditStart(&instrument->cmdstate, -1, -1, instrument->cmdbuf, sizeof(instrument->cmdbuf), "^_- "); + instrument->is_ready = 0; + } - printf("msg: %s (%ld)\n", cmdline, cmdlength); + FD_ZERO(&readfds); + FD_SET(instrument->cmdstate.ifd, &readfds); + timeout.tv_sec = 1; + timeout.tv_usec = 0; - if(parse_message_from_cmdline(cmdline, cmdlength, &instrument->cmd) < 0) { - syslog(LOG_ERR, "Could not parse message from cmdline %s\n", cmdline); + 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)); return -1; - } + } - free(cmdline); + // Yield for foreground bookkeeping on timeout + if(ret == 0) return 0; - return 0; + return astrid_instrument_process_command_tick(instrument); } diff --git a/astrid/src/astrid.h b/astrid/src/astrid.h index f1d8efa..a12bcf9 100644 --- a/astrid/src/astrid.h +++ b/astrid/src/astrid.h @@ -141,11 +141,16 @@ typedef struct lpscheduler_t { typedef struct lpinstrument_t { char * name; int channels; - volatile int is_running; - volatile int is_waiting; + 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; + // linenoise cmd buf + struct linenoiseState cmdstate; + char cmdbuf[1024]; + // LMDB session refs MDB_cursor * dbcur; MDB_env * dbenv; @@ -159,6 +164,8 @@ typedef struct lpinstrument_t { // 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 // The instrument message q(s) char qname[NAME_MAX]; @@ -272,6 +279,7 @@ ssize_t astrid_get_voice_id(); int decode_update_message_param(lpmsg_t * msg, uint16_t * id, float * value); int encode_update_message_param(lpmsg_t * msg); +int init_instrument_message(lpmsg_t * msg, char * instrument_name); int send_message(char * qname, lpmsg_t msg); int send_play_message(lpmsg_t msg); int send_serial_message(lpmsg_t msg); @@ -311,12 +319,14 @@ int astrid_get_playback_device_id(); int astrid_get_capture_device_id(); lpbuffer_t * lpsampler_aquire_and_map(char * name); +int lpsampler_release_and_unmap(char * name, lpbuffer_t * buf); int lpsampler_aquire(char * name); int lpsampler_release(char * name); int lpsampler_read_ringbuffer_block(char * name, lpbuffer_t * buf, size_t offset_in_frames, lpbuffer_t * out); int lpsampler_write_ringbuffer_block(char * name, lpbuffer_t * buf, float ** block, int channels, size_t blocksize_in_frames); lpbuffer_t * lpsampler_create(char * name, double length_in_seconds, int channels, int samplerate); int lpsampler_destroy(char * name); +int lpsampler_destroy_and_unmap(char * name, lpbuffer_t * buf); int lpipc_setid(char * path, int id); int lpipc_getid(char * path); @@ -329,7 +339,21 @@ int lpipc_destroyvalue(char * id_path); void lptimeit_since(struct timespec * start); -lpinstrument_t * astrid_instrument_start(char * name, int channels, int ext_relay_enabled, double adc_length, void * ctx, char * tty, char * midi_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( + char * name, + int channels, + int ext_relay_enabled, + double adc_length, + double resampler_length, + void * ctx, + char * tty, + char * midi_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) + ); + int astrid_instrument_stop(lpinstrument_t * instrument); int32_t astrid_instrument_get_param_int32(lpinstrument_t * instrument, int param_index, int32_t default_value); @@ -350,7 +374,7 @@ int astrid_instrument_session_open(lpinstrument_t * instrument); int astrid_instrument_session_close(lpinstrument_t * instrument); int astrid_instrument_publish_bufstr(char * instrument_name, unsigned char * bufstr, size_t size); int send_render_to_mixer(lpinstrument_t * instrument, lpbuffer_t * buf); -int relay_message_to_seq(lpinstrument_t * instrument); +int relay_message_to_seq(lpinstrument_t * instrument, lpmsg_t msg); int extract_int32_from_token(char * token, int32_t * val); int extract_float_from_token(char * token, float * val); diff --git a/libpippi/src/pippicore.c b/libpippi/src/pippicore.c index 7fb76cb..29c96d8 100644 --- a/libpippi/src/pippicore.c +++ b/libpippi/src/pippicore.c @@ -1942,6 +1942,10 @@ lpfloat_t lpzapgremlins(lpfloat_t x) { return (absx > (lpfloat_t)1e-15 && absx < (lpfloat_t)1e15) ? x : (lpfloat_t)0.f; } +lpfloat_t lpfilternan(lpfloat_t x) { + return isnan(x) ? 0.f : x; +} + lpfloat_t lpwv(lpfloat_t value, lpfloat_t min, lpfloat_t max) { /* wrap value */ if(value > max) value -= max; diff --git a/libpippi/src/pippicore.h b/libpippi/src/pippicore.h index 1499f8b..af9bce5 100644 --- a/libpippi/src/pippicore.h +++ b/libpippi/src/pippicore.h @@ -220,6 +220,8 @@ extern const lpinterpolation_factory_t LPInterpolation; */ lpfloat_t lpzapgremlins(lpfloat_t x); +lpfloat_t lpfilternan(lpfloat_t x); + /* These are little value scaling helper routines. */ /* lpwv wraps a given value between min and max through probably wrong basic arithmetic */ lpfloat_t lpwv(lpfloat_t value, lpfloat_t min, lpfloat_t max); diff --git a/pippi/fx.pyx b/pippi/fx.pyx index a33f23f..c803a37 100644 --- a/pippi/fx.pyx +++ b/pippi/fx.pyx @@ -227,6 +227,9 @@ cdef double[:,:] _norm(double[:,:] snd, double ceiling): cdef double normval = 1 cdef double maxval = _mag(snd) + if maxval == 0: + return snd + normval = ceiling / maxval for i in range(framelength): for c in range(channels): diff --git a/pippi/renderer.pxd b/pippi/renderer.pxd index 93755e1..c957c73 100644 --- a/pippi/renderer.pxd +++ b/pippi/renderer.pxd @@ -9,6 +9,8 @@ cdef extern from "stdint.h": cdef extern from "pippicore.h": ctypedef double lpfloat_t + lpfloat_t lpzapgremlins(lpfloat_t x); + lpfloat_t lpfilternan(lpfloat_t x); u_int32_t lphashstr(char * str) ctypedef struct lpbuffer_t: @@ -115,6 +117,8 @@ cdef extern from "astrid.h": char adcname[4096] lpbuffer_t * adcbuf + char resamplername[4096] + lpbuffer_t * resamplerbuf char qname[NAME_MAX] char external_relay_name[NAME_MAX] @@ -125,7 +129,15 @@ cdef extern from "astrid.h": lpscheduler_t * async_mixer - int lpsampler_read_ringbuffer_block(char * name, lpbuffer_t * buf, size_t offset_in_frames, lpbuffer_t * out) + lpbuffer_t * lpsampler_aquire_and_map(char * name); + int lpsampler_release_and_unmap(char * name, lpbuffer_t * buf); + int lpsampler_aquire(char * name); + int lpsampler_release(char * name); + int lpsampler_read_ringbuffer_block(char * name, lpbuffer_t * buf, size_t offset_in_frames, lpbuffer_t * out); + int lpsampler_write_ringbuffer_block(char * name, lpbuffer_t * buf, float ** block, int channels, size_t blocksize_in_frames); + lpbuffer_t * lpsampler_create(char * name, double length_in_seconds, int channels, int samplerate); + int lpsampler_destroy(char * name); + int lpsampler_destroy_and_unmap(char * name, lpbuffer_t * buf); int lpipc_getid(char * path) ssize_t astrid_get_voice_id() @@ -172,6 +184,7 @@ cdef extern from "astrid.h": int channels, int ext_relay_enabled, double adc_length, + double resampler_length, void * ctx, char * tty, char * midi_device_name, @@ -225,13 +238,18 @@ cdef class Instrument: cdef public lpmsg_t msg # a copy of the last message received cdef public size_t last_reload cdef public double max_processing_time + cdef public int channels + 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 lpinstrument_t * i - cpdef EventContext get_event_context(Instrument self, bint with_graph=*) + 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_from_sampler(Instrument self, str name) + cdef void save_to_sampler(Instrument self, str name, SoundBuffer snd) cdef class SessionParamBucket: cdef Instrument instrument @@ -254,7 +272,7 @@ cdef class EventContext: cdef public Instrument instrument cdef tuple collect_players(Instrument instrument) -cdef int render_event(Instrument instrument) +cdef int render_event(Instrument instrument, str msgstr) cdef set collect_trigger_planners(Instrument instrument) cdef int trigger_events(Instrument instrument) diff --git a/pippi/renderer.pyx b/pippi/renderer.pyx index 9bc52fe..a00f38b 100644 --- a/pippi/renderer.pyx +++ b/pippi/renderer.pyx @@ -25,7 +25,7 @@ cimport numpy as np from pippi import dsp, midi, ugens from pippi.soundbuffer cimport SoundBuffer -NUM_COMRADES = 4 +NUM_COMRADES = 16 class InstrumentError(Exception): pass @@ -68,7 +68,7 @@ cdef bytes serialize_buffer(SoundBuffer buf, int is_looping, lpmsg_t msg): for i in range(length): for c in range(channels): - strbuf += struct.pack('d', buf.frames[i,c]) + strbuf += struct.pack('d', lpfilternan(buf.frames[i,c])) msgview = (&msg) for i in range(msgsize): @@ -86,6 +86,8 @@ cdef class MessageEvent: ): cdef size_t onset_frames = 0 + logger.error('MessageEvent params: %s' % params) + params_byte_string = params.encode('utf-8') instrument_name_byte_string = instrument_name.encode('utf-8') cdef char * byte_params = params_byte_string @@ -199,6 +201,8 @@ cdef class EventTriggerFactory: params += ' ' params += ' '.join([ '%s=%s' % (k, v) for k, v in kwargs.items() ]) + logger.error('_parse_params params=%s' % params) + 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): @@ -342,7 +346,7 @@ cdef class EventContext: self.graph = graph self.cache = cache - self.p = ParamBucket(str(msg)) + self.p = ParamBucket(msg) self.s = SessionParamBucket(instrument) self.t = EventTriggerFactory() self.m = MidiEventListenerProxy(default_midi_device) @@ -357,6 +361,14 @@ 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): + if snd is None: + return self.instrument.read_from_sampler(name) + self.instrument.save_to_sampler(name, snd) + + 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) + def log(self, msg): logger.info('ctx.log[%s] %s' % (self.instrument_name, msg)) @@ -364,7 +376,7 @@ cdef class EventContext: return self.p._params cdef class Instrument: - def __cinit__(self, str name, str path, int channels, double adc_length, str midi_device_name): + 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: @@ -385,12 +397,16 @@ cdef class Instrument: self.last_reload = 0 self.max_processing_time = 0 - self.i = astrid_instrument_start(self.ascii_name, channels, 1, adc_length, NULL, NULL, + self.i = astrid_instrument_start(self.ascii_name, channels, 1, adc_length, resampler_length, NULL, NULL, self.midi_device_name, NULL, NULL, NULL, NULL) if self.i == NULL: raise InstrumentError('Could not initialize lpinstrument_t') + # eh, why not. might be useful if the Instrument is available in the ctx too + self.samplerate = self.i.samplerate + self.channels = self.i.channels + self.load_renderer(name, path) def __dealloc__(self): @@ -462,14 +478,17 @@ cdef class Instrument: self.msg = msg # so many copies omg return msg - cpdef EventContext get_event_context(Instrument self, bint with_graph=False): + cpdef EventContext get_event_context(Instrument self, str msgstr=None, bint with_graph=False): cdef bytes render_params = self.msg.msg - msgstr = '' - if self.msg.type != LPMSG_MIDI_FROM_DEVICE: - # FIXME use the is_encoded flag or something... - # or maybe I can ignore this and just decode from utf-8 like a criminal... - msgstr = render_params.decode('ascii') + if msgstr is None: + msgstr = '' + if self.msg.type != LPMSG_MIDI_FROM_DEVICE: + # FIXME use the is_encoded flag or something... + # or maybe I can ignore this and just decode from utf-8 like a criminal... + msgstr = render_params.decode('ascii') + + logger.error('PLAY PARAMS: %s' % msgstr) graph = None if with_graph: @@ -510,6 +529,92 @@ cdef class Instrument: return snd + cdef SoundBuffer read_from_resampler(Instrument self, double length, double offset=0, int channels=2, int samplerate=48000, str instrument=None): + cdef size_t i + cdef int c + cdef lpbuffer_t * out + cdef char * resampler_name + + cdef SoundBuffer snd = SoundBuffer(length=length, channels=channels, samplerate=samplerate) + cdef size_t length_in_frames = len(snd) + cdef size_t offset_in_frames = (offset * samplerate) + + out = LPBuffer.create(length_in_frames, channels, samplerate) + + if instrument is None: + resampler_name = self.i.resamplername + else: + instrument = '%s-resampler' % instrument + resampler_name_bytes = instrument.encode('UTF-8') + resampler_name = resampler_name_bytes + + if lpsampler_read_ringbuffer_block(resampler_name, self.i.resamplerbuf, offset_in_frames, out) < 0: + logger.error('pippi.renderer resampler read: failed to read %d frames at offset %d from resampler' % (length_in_frames, offset_in_frames)) + return snd + + for i in range(length_in_frames): + for c in range(channels): + snd.frames[i,c] = lpfilternan(out.data[i * channels + c]) + + LPBuffer.destroy(out) + + return snd + + cdef SoundBuffer read_from_sampler(Instrument self, str name): + cdef size_t i + cdef int c + cdef lpbuffer_t * out + cdef SoundBuffer snd + + 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: + 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.channels, samplerate=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('BLOWUP! self.channels=%d self.samplerate=%s' % (self.channels, self.samplerate)) + return None + + for i in range(out.length): + for c in range(out.channels): + snd.frames[i,c] = out.data[i * out.channels + c] + + if lpsampler_release_and_unmap(_name, out) < 0: + logger.error('Could not release %s sampler memory' % _name) + + return snd + + cdef void save_to_sampler(Instrument self, str name, SoundBuffer snd): + cdef size_t i + cdef int c + cdef lpbuffer_t * out + + name_bytes = name.encode('UTF-8') + cdef char * _name = name_bytes + + # create the shm buffer + logger.error('Creating %s buffer with frame 10,0=%s' % (name, snd.frames[10,0])) + out = lpsampler_create(_name, snd.dur, snd.channels, self.samplerate) + logger.error('out.length=%s len(snd)=%s' % (out.length, len(snd))) + + # write the data into it + for i in range(out.length): + 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) + + 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) @@ -630,7 +735,7 @@ cdef tuple collect_players(Instrument instrument): return players, loop -cdef int render_event(Instrument instrument): +cdef int render_event(Instrument instrument, str msgstr): cdef set players cdef bint loop cdef bytes bufstr @@ -640,9 +745,9 @@ cdef int render_event(Instrument instrument): instrument_byte_string = instrument.name.encode('UTF-8') cdef char * _instrument_ascii_name = instrument_byte_string - ctx = instrument.get_event_context() + ctx = instrument.get_event_context(msgstr) - logger.debug('rendering event %s w/params %s' % (str(instrument), render_params)) + logger.debug('rendering event %s w/params %s' % (str(instrument), msgstr)) if hasattr(instrument.renderer, 'before'): instrument.renderer.before(ctx) @@ -780,7 +885,7 @@ def render_executor(Instrument instrument, object q, int comrade_id): logger.exception('Error getting now seconds') return - if render_event(instrument) < 0: + if render_event(instrument, msg) < 0: logger.exception('Error trying to execute python render...') return @@ -810,6 +915,7 @@ def _run_forever(Instrument instrument, str instrument_name, int channels, double adc_length, + double resampler_length, object q, ): cdef lpmsg_t msg @@ -845,7 +951,8 @@ def _run_forever(Instrument instrument, elif msg.type == LPMSG_PLAY: logger.info('PY MSG: play') - q.put(1) + logger.error('PY MSG: play params: %s' % msg.msg) + q.put(msg.msg.decode('utf-8')) elif msg.type == LPMSG_TRIGGER: logger.info('PY MSG: trigger') @@ -856,7 +963,7 @@ def _run_forever(Instrument instrument, # FIXME pass this along to the comrades try: if instrument is None: - instrument = Instrument(instrument_name, script_path, channels, adc_length) + instrument = Instrument(instrument_name, script_path, channels, adc_length, resampler_length) else: last_edit = os.path.getmtime(instrument.path) if last_edit > instrument.last_reload: @@ -868,7 +975,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, 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_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') @@ -877,7 +984,7 @@ 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, midi_device_name) + instrument = Instrument(instrument_name, script_path, channels, adc_length, resampler_length, midi_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...') @@ -890,18 +997,26 @@ def run_forever(str script_path, str instrument_name=None, int channels=2, doubl comrade.start() render_pool += [ comrade ] - message_process = Process(target=_run_forever, args=(instrument, script_path, instrument_name, channels, adc_length, render_q)) + message_process = Process(target=_run_forever, args=(instrument, script_path, instrument_name, channels, adc_length, resampler_length, render_q)) message_process.start() try: 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 + # times out after a second to allow for render pool cleanup if astrid_instrument_tick(instrument.i) < 0: logger.info('PY: Could not read console line') time.sleep(2) continue + for comrade in render_pool: + if not comrade.is_alive(): + render_pool.remove(comrade) + logger.error('Our comrade has become exhausted, and is being relieved') + comrade = Process(target=render_executor, args=(instrument, render_q, i)) + comrade.start() + render_pool += [ comrade ] + except KeyboardInterrupt as e: print('PY: Got keyboard interrupt') diff --git a/pippi/soundbuffer.pyx b/pippi/soundbuffer.pyx index be99802..f304706 100644 --- a/pippi/soundbuffer.pyx +++ b/pippi/soundbuffer.pyx @@ -281,10 +281,20 @@ cdef class SoundBuffer: int samplerate=DEFAULT_SAMPLERATE, object filename=None, int offset=0, - double[:,:] buf=None): + double[:,:] buf=None, int framelength=-1): self.samplerate = samplerate self.channels = max(channels, 1) - cdef int framelength = (length * self.samplerate) + + if frames is not None: + framelength = len(frames) + + if length >= 0: + framelength = (length * self.samplerate) + elif framelength >= 0: + length = framelength * samplerate + else: + raise ValueError('Missing length param') + cdef double[:] tmplist if filename is not None: -- 2.45.2