Param handling is coming along. I decided not to encode messages at all. Instead I’m keeping everything in a string format until the instrument gets the update message in the message thread. At that point it calls a param map callback defined on the instrument with the string versions of each of the param names and values and lets the instrument decide how to do the decoding.
It’s not the most elegant thing in the world. I mapped out some params
over lunch today and it’s already getting kinda wordy. The
extract_<type>from_token functions decode the
string representations of the values into the appropriate type, and the
astrid_instrument_set_param<type> functions set
the raw decoded value (usually a float, but can be an int, float list or
pattern buffer, as well as other new types in the future) into the LMDB
session where they can be safely read back from the audio thread without
blocking.
It’s simple enough for now though and I think the eventual python version of it can be more of a simple map of names to types, maybe with optional scaling or etc.
the param map callback
int param_map_callback(void * arg, char * keystr, char * valstr) {
lpinstrument_t * instrument = (lpinstrument_t *)arg;
localctx_t * ctx = (localctx_t *)instrument->context;
float val_f = 0;
uint32_t val_i32 = 0;
lppatternbuf_t val_pattern = {1,{1}};
if(strcmp(keystr, "oamp") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_AMP, val_f);
} else if(strcmp(keystr, "omix") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_MIX, val_f);
} else if(strcmp(keystr, "opw") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_PULSEWIDTH, val_f);
} else if(strcmp(keystr, "osat") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_SATURATION, val_f);
} else if(strcmp(keystr, "ospeed") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_ENVELOPE_SPEED, val_f);
} else if(strcmp(keystr, "odrift") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_DRIFT_DEPTH, val_f);
} else if(strcmp(keystr, "odist") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_OSC_DISTORTION_AMOUNT, val_f);
} else if(strcmp(keystr, "octspread") == 0) {
extract_int32_from_token(valstr, &val_i32);
astrid_instrument_set_param_int32(instrument, PARAM_OSC_OCTAVE_SPREAD, val_i32);
} 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, "freqs") == 0) {
extract_int32_from_token(valstr, &val_i32);
ctx->selected_freqs[LPRand.randint(0, NUMFREQS)] = scale[val_i32 % NUMFREQS] * 0.5f + LPRand.rand(0.f, 1.f);
astrid_instrument_set_param_float_list(instrument, PARAM_OSC_FREQS, ctx->selected_freqs, NUMFREQS);
} else if(strcmp(keystr, "gamp") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_GATE_AMP, val_f);
} else if(strcmp(keystr, "gmix") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_GATE_MIX, val_f);
} else if(strcmp(keystr, "gspeed") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_GATE_SPEED, 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);
} else if(strcmp(keystr, "gsat") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_GATE_SATURATION, val_f);
} else if(strcmp(keystr, "gshape") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_GATE_SHAPE, val_f);
} else if(strcmp(keystr, "gdrift") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_GATE_DRIFT_DEPTH, val_f);
} else if(strcmp(keystr, "gpat") == 0) {
extract_patternbuf_from_token(valstr, val_pattern.pattern, &val_pattern.length);
astrid_instrument_set_param_patternbuf(instrument, PARAM_GATE_PATTERN, &val_pattern);
} else if(strcmp(keystr, "mmix") == 0) {
extract_float_from_token(valstr, &val_f);
astrid_instrument_set_param_float(instrument, PARAM_MIC_MIX, val_f);
}
return 0;
}
I want to add some more param types though, to take frequency lists at
least, maybe some chord conversion? But probably all the preparation of
the frequency lists can happen in python where the tune
module is available for constructing harmony. All the instruments need
to be able to do for now is accept a list of frequencies.
This also means I’m sending param updates via serial as command strings
rather than encoded payloads. It simplifies the daisy firmware concerns
a bit, too. (Even tho it’s more annoying to work with strings than just
memcpy some bytes into a field, that’s OK.)
Here’s some playing around with the C instrument (littlefield) I’m working on now being sequenced from another python instrument script (littleseq) which has a trigger callback that sequences sending update messages to littlefield via the new param mappings.
Update: almost feels like a real instrument with all the params mapped and some basic interactivity going!