Monday, January 27, 2014

LV2 forge and Tempo syncronization, or: More LV2 Learnings (still not quite a tutorial)

Ah so the urid map must be persistent...


I was having a hard time getting an atom forge to generate midi events for me. The secret was when you intialize the forge, you must point to to a location thats not about to get deallocated. While this is done in the examples, it isn't explicitly stated that this is required. Since I have a way of wanting to write everything myself rather than copy and paste, it was a detail I missed. To show you some code, this part is where we get the urid maps and initialize the forge (done in the plugin initialization function):
ENVFOLLOWER* plug = malloc(sizeof(ENVFOLLOWER)); LV2_URID_Map *urid_map; ... //get urid map value for midi events for (int i = 0; host_features[i]; i++) { if (strcmp(host_features[i]->URI, LV2_URID__map) == 0) { plug->urid_map = (LV2_URID_Map *) host_features[i]->data; if (plug->urid_map) { plug->midi_event_type = plug->urid_map->map(plug->urid_map->handle, LV2_MIDI__MidiEvent); break; } } } lv2_atom_forge_init(&plug->forge,plug->urid_map); return plug;
(If you didn't notice already this is from my envelope follower plugin). So we have a member of the plugin structure to store the forge and the map, and pass them both in to the library function lv2_atom_forge_init. That's not too bad. Just make sure you keep that urid_map variable around, even if you aren't ever going to access it directly.

So a forge is (according to my understanding) just an object that creates atoms or other objects to be output from the plugin, including midi messages (which is the most common use I'd venture). I'm out on a limb here, but I'm guessing its the safe way to allocate memory and pass it out of the plugin so that you aren't trying to malloc in the RT thread, and so it can still be in scope once it leaves the plugin.

Now in the run function of the plugin, we first associate the forge with the midi output port, and prepare a local atom to hold our midi message:
LV2_Atom midiatom; unsigned char msg[3]; ... //get midi port ready const uint32_t capacity = plug->midi_out_p->atom.size; lv2_atom_forge_set_buffer(&plug->forge,(uint8_t*)plug->midi_out_p, capacity); lv2_atom_forge_sequence_head(&plug->forge, &plug->frame, 0); midiatom.type = plug->midi_event_type; midiatom.size = 3;//midi CC
Note that the member of the plugin structure midi_out_p is declared as an LV2_Atom_Sequence*. I have no idea what exactly the function call lv2_atom_forge_sequence_head does, but I assume its a sort of syncronization.

With this plugin we send a midi CC proportional to the envelope of the audio input signal. Once we've calculated what the amplitude is/what the CC value should be we load the message with its parameters (read about midi spec if you need to know more here, but the first byte is the channel, second the control number, third the value). We forge a new message into the port buffer through several steps. First set the time, or the frame index this message corresponds to in this period (below it's variable i). Next you forge on the local atom which has the members showing its a midi event type with size 3. Then you forge the bytes that the message should carry. Finally we pad the atom which I think separates the events in the buffer. I believe this must be done before another message can be forged. The whole process looks like this:
//make event msg[0] = MIDI_CONTROL_CHANGE + (unsigned char)(*plug->channel_p - 1); msg[1] = MIDI_DATA_MASK & (unsigned char)*plug->control_p; msg[2] = MIDI_DATA_MASK & plug->mout; midiatom.type = plug->midi_event_type; midiatom.size = 3;//midi CC lv2_atom_forge_frame_time(&plug->forge,i); lv2_atom_forge_raw(&plug->forge,&midiatom,sizeof(LV2_Atom)); lv2_atom_forge_raw(&plug->forge,msg,3); lv2_atom_forge_pad(&plug->forge,3+sizeof(LV2_Atom));
And that's it. You can repeat that last block as many times as you need (so long as you aren't producing more messages than frames or samples in the buffer period) and the LV2 framework handles the rest. Sending midi notes or program changes all go right along the same lines, you'd just have different constants in the second byte of the message. I got this from example code and without a better understanding of the forges, and atoms, and atom ports, you're stuck with my marginally accurate explanations. Perhaps you're better off just looking at the example plugins, but I'm trying my best. My code is GPL so you are free to copy this directly.

For my next trick I'll try to explain how to tempo sync parameters. This was a feature added upon request to the Cellular Automaton Synth. Its the type of feature that makes a user feel like they're really good (since its a lot easier to make patches that fit in a song) and its really not hard to do. In a synth you have an atom input port anyway for the midi input to control it, but you can have an atom input that only passes the time info. I'm not going to show you how, because I've never done it myself. But here's how with a full midi input port and the principles stand alone.

The idea is that the host passes timing information to your plugin through the atom port. So instead of a midi atom you suddenly receive this other kind of atom. To recognize it, you'll need to have the URID mapping:
//get urid map value for midi events for (int i = 0; host_features[i]; i++) { if (strcmp(host_features[i]->URI, LV2_URID__map) == 0) { LV2_URID_Map *urid_map = (LV2_URID_Map *) host_features[i]->data; if (urid_map) { synth->midi_event_type = urid_map->map(urid_map->handle, LV2_MIDI__MidiEvent); synth->other_type = urid_map->map(urid_map->handle, LV2_ATOM__Blank); synth->long_type = urid_map->map(urid_map->handle, LV2_ATOM__Long); synth->float_type = urid_map->map(urid_map->handle, LV2_ATOM__Float); synth->time_info_type = urid_map->map(urid_map->handle, LV2_TIME__Position); synth->beatsperbar_type = urid_map->map(urid_map->handle, LV2_TIME__barBeat); synth->bpm_type = urid_map->map(urid_map->handle, LV2_TIME__beatsPerMinute); synth->speed_type = urid_map->map(urid_map->handle, LV2_TIME__speed); synth->frame_type = urid_map->map(urid_map->handle, LV2_TIME__frame); synth->framespersec_type = urid_map->map(urid_map->handle, LV2_TIME__framesPerSecond); break; } } }
So you can see in addition to the midi_event_type URID* there are a lot of other type of atoms' URIDs, the most important one in this context being the beats per minute, but to get that actual data, you'll need several of the other atom types' URIDs as well. We treat the input from the atom port the same as we did before, checking to see if there's a midi message, but now we also check for this timing information:
LV2_ATOM_SEQUENCE_FOREACH(synth->midi_in_p, event) { if (event) { if(event->body.type == synth->midi_event_type)//make sure its a midi event { ... }//actually midi else if(event->body.type == synth->other_type) { // Received new transport position/speed const LV2_Atom_Object *obj = (LV2_Atom_Object*)&event->body; if(obj->body.otype == synth->time_info_type) { LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; LV2_Atom *fps = NULL, *frame = NULL; lv2_atom_object_get(obj, synth->beatsperbar_type, &beat, synth->bpm_type, &bpm, synth->speed_type, &speed, synth->frame_type, &frame, synth->framespersec_type, &fps,NULL); if (fps && fps->type == synth->float_type) { synth->sample_rate = ((LV2_Atom_Float*)fps)->body; } if (bpm && bpm->type == synth->float_type) { // Tempo changed, update BPM synth->ibpm = 60/(((LV2_Atom_Float*)bpm)->body); synth->cell_lifetime = synth->sample_rate*(*synth->cell_life_p)*synth->ibpm; } }//if blank type }//actually is event }//for each event ... }
If its not a midi message, check to see if its an "other message" (really called a blank atom by convention*). If so, cast the atom data into another atom object, and see if it matches the time_info_type URID you got from the mapping in the initialization. If its a match, we can finally extract the data using the function lv2_atom_object_get which is in the LV2 library and pulls various data out that matches the URIDs you pass in. I then look at the sample rate (though I anticipate it won't change, but just in case), but more significantly the beats per minute. These bits of data in the "blank" atom (or "other" as I like to think of it) are also in atoms (from atoms in atoms in an atom) but you can access the data as shown. The math isn't hard, but in general you're probably most interested in knowing how many frames per beat. (60 sec/min )/(beats/min)*(frames/sec) == frames/beat (to spell it out for you). I have an input port where the user can select how many beats are in the cell lifetime (i.e. cell state switches every beat, half beat, 4 beats, etc.) so this scales the result to make the final value that I store the number of frames per cell lifetime.

I employ this value when processing the frames, I count the number that have elapsed and when the time expires, I just reset a counter. My implementation is flawed in that if it is a non-integer number of frames I don't account for that. I should keep track and have a "leap frame" that keeps it on track. So the sound will actually drift over time, but I have yet to have noticed when playing with it. But this is great for LFOs and other time dependent parameters.

This post has been kicking around half finished in the drafts pile for months now, and I've just about forgotten how to do it. So I need to write this down for myself more than for anybody else. But, Mr. Else, I hope you find it helpful too. As always see my plugins' source at and feel free to ask questions.

*EDIT: This section has been revamped a bit to try to make it more accurrate. Thanks to Drobilla for helping me understand (see his comment below).

3 comments:

Unknown said...

Hello. There are several problems with this code: one major bug, and some minor things that just make it confusing.

The bug is:

if (fps && fps->type == synth->float_type)
{
synth->sample_rate = ((LV2_Atom_Float*)frame)->body;
}

You mean "fps", not "frame".

For readability, the main reason this code is so confusing is that the usual naming conventions are not used and some things have outright misleading names.

Mainly, your URID names don't follow the prefix_name convention. Usually, atom:Blank is atom_Blank, and so on. Nobody knows what synth->other_type means. A bigger problem is that things are called types that aren't even types at all, e.g. synth->speed_type is actually time:speed, which is not a type, it's a property. The conventional name for this URID would be time_speed, which makes it clear what it is, wherever in the code you encounter it. A minor thing that can save you some hassle is to just use the type URIDs in the forge rather than remapping e.g. atom:Float yourself.

In general, you'll find atom-using code much easier to write and understand if you follow the conventions used in the examples (that's what they're there for). The metro example is a simple one that follows tempo information.

Spencer said...

Excellent catch on the bug. Thanks.

Perhaps (even probably) this is more confusing, but part of the reason I diverged from the name convention is because it didn't make sense to me (and I didn't realize it was a convention). I hoped by providing an additional example that looked slightly different would help some people in understanding.

Your comments about types vs properties are valuable and aid my understanding. Perhaps if I had majored in CS these principles would be obvious, but this isn't the case. I'll see if I can fix it up.

I'm not sure what you mean by "use the type URIDs in the forge rather than remapping atom:Float." Perhaps you are referring to my use of lv2_atom_forge_raw?

Atoms and forges are still rather unclear to me, but through the examples and others' plugins I wound up with my current understanding, albeit flawed. I appreciate any clarification, or explanation you offer.

Unknown said...

Thanks Spencer

This helps me to convert my plug from the old event extension to LV2_Atom_Sequence.

regards
hermann