May

double densed uncounthest hour of allbleakest age with a bad of wind and a barrel of rain

double densed uncounthest hour of allbleakest age with a bad of wind and a barrel of rain is an in-progress piece for resonators and brass. I’m keeping a composition log here as I work on it.

There are sure to be many detours. Getting it in shape might involve:

Friday May 31st

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!

Sunday May 26th

Got serial control working this weekend! Here’s a very exciting video demonstrating a volume control mapped to an amplitude parameter in the test instrument:

I’m working my way toward sorting through the param handling stuff:

For example, the cmdline u amp=0.5 freq=220 gets parsed by [C] into two lpmsg_t update messages via [p] into payloads which are IDs (mapped to an enum in the instrument) and float values encoded into the msg field of the update message, then relayed back via [M] and finally received by [U] for processing.

The video above is a simpler version of this flow that doesn’t include the [C] console parsing. That’s tonight’s project!

The procedure is basically:

The way encoding happens is determined by the instrument [P] callback, which means instruments get to decide what kinds of params they support and how to deal with them…

Monday May 20th

I’d like to share this essay I wrote about process music for Roots/Routes:

Where do you place agency in a process-based music system?

Sunday May 19th

Here’s something of a plan…

On the microcontroller side messages get sent as lpserialmsg_t structs:

typedef struct lpserialmsg_t {
    uint16_t type; // corresponds to LPMessageTypes enum used by normal lpmsg_t messages
    size_t size; // when greater than 0, the following bytes have a data payload of `size`
    char instrument_name[LPMAXNAME]; // the instrument name, for message routing
} lpserialmsg_t;

This allows a press-a-button send-a-play-message situation – mapping any control to some astrid message.

When the size field is greater than 0, the listener thread should read size more bytes via serial before processing the message. For now, this is only used as the payload for update messages when sending MIDI-like id/value pairs. It could be expanded for other message types (a DATA or BUFFER message?) to support sending arbitrary data like a stream of onsets, or whatever. I’m leaving that unimplemented for now since I’m still not sure if I need it yet. It also occurred to me since the lpserialmsg_t messages can relay play messages to any instrument, I could just do my onset recordings on the microcontroller, and send sequences via play messages over serial rather than scheduling them in astrid…

This feels a little more sane. I still have some things to think about w/r/t encoding update messages, but I’m leaning toward letting them just be always encoded – in other words, never writing the plain text params into the msg field of the update message, but always using it as a buffer to store the id/value. It could probably use a better name than “update” at some point.

Saturday May 18th

The project of the moment is to get serial messaging bidirectional in some kind of sane, useful way… I haven’t touched pretty much anything in astrid or pippi land in more than a week. (For a nice reason! I got really hooked on a book my cousin lent me last week.)

The hard part about this (as usual) is trying to decide on the structure of the data. None of my ideas are fancy. Some are decided:

There are lots of things to figure out from there:

After typing all that out, I’m starting to wonder again if the payload should be variable, and I just need to deal up front with it in the serial reader?

Let’s say I use 64 bits for the value: it can hold a double, a long int, and a 64 step pattern… but if I wanted to hold a 61 step pattern…?

Maybe the last (or first) byte of the payload for a pattern type can hold the length of the pattern, which still leaves enough room for 60 steps to be encoded.

What about just using a header?? Maybe this is actually simpler…

No answers. None of it seems apparent.

Saturday May 4th

I was planning to go camping today, but it was a pretty crappy rainy day and I decided to stay in… I tried mounting some of the amplifier into the box I was thinking of using for controls… didn’t feel really great about any of it, and I’m starting to think I just want to use two channels anyway.

I’m thinking about just adapting the pulsar test drone, adding some more controls but keeping it probably pretty simple in terms of inputs and outputs… I’m not really sure, but I felt kind of overwhelmed by trying to figure out the mixer stuff I wanted to build into it all… and realized I can do it in software if I break out some controls instead…

Friday May 3rd

Hooray, I hit a bit of a milestone today! I’m pretty sure there are still some memory leaks, and I’ve got some work on the new sampler left to do… however it’s pretty exciting to be testing out some coordinated events with a little astrid script orchestrating the solenoid wiggling and the pulsar bloops this morning.

What I’d like to do in the meantime (while finishing the hardware build and testing the four channel setup, adding motor control, etc…) is keep prototyping some instrument scripts in python, and then port them to C instruments when I’m happy with them.

Hopefully there will be time for it, I’m not really super into live-coding during performance (though it’s a great way to prototype & compose) and would like to map all controls to the arduino’s inputs along with some simple orchestration commands at the astrid prompt…

Porting the instrument to C should let me get a little more fancy in the render callback, and the stream interface only exists in C at the moment, that would be nice to use… we’ll see!


Log April 2024

Log March 2024

Log February 2024

Log January 2024

Log December 2023