MIDI transpose patch example


#1

Ok, so this is probably the most wasteful usage of an Axoloti, but I wanted to see how it would be to do some MIDI processing, as well as get some exercise in patches and object writing.

What I wanted is a MIDI transpose function that I can connect to any MIDI keyboard which will simply shift the MIDI notes up or down by octaves. As an added complexity, I want it to work even if the transpose button is pressed while notes are held down, so simply shifting the note number won't work. And I want it to work polyphonically too.

So to this avail I created a MIDI transpose object, which takes MIDI in data on any channel, transposes it by an amount governed by its input, and outputs it on the same channel.

In the patch, the transposition is controlled by using MIDI program change: MIDI program numbers 0,1,2,3,4,5,6,7 set the transposition to -4,-3-,2-,1,0,1,2 or 3 octaves, respectively. Actually, any group of divisible-by-8 program numbers achieve the same thing.

I noticed by the way, that the MIDI input objects such as midi/in/pgm have an internal channel attribute that is undefined, so they only react to channel 1 (I suppose undefined attributes default to 0), and the channel cannot be set. So currently, the patch only responds to program changes on channel 0 even though the actual transposition can take place on any channel.

A limitation currently with the transpose object is that it only functions correctly one a single channel at a time. It has an internal vector storing the transpositions at the time of the note on in order to retain that transposition for the subsequent note off, and all note on events regardless of channel are stored in the vector. To do this properly would require a multi-dimensional vector, but that would head up 16 times more memory too (ok, so it only ends up being about 2 kbytes in the end, but still...). Room for future improvement.

The sample-and-hold is there so that a new transposition is only performed when a program change is actually received; since the output from the midi/in/pgm object defaults to 0, that would give a start-up transposition of -4 octaves otherwise.

There's also the added bonus of the red led on the Axoloti board lighting up every time a note on is received. It's not part of the transpose function.

I wasn't sure where to put the transpose object definition (I'm using version 1.0.6). It is currently in axoloti/objects/midi .

miditranspose.axp (4.7 KB)

transpose.axo (2.4 KB)


Transpose external sequences
#2

Oh, Id not say its a waste of Axoloti resources, to the contrary I think of axoloti as a swiss army knife, you can use it for all sorts of things, sometimes simultaneous, sometimes just for a particular use.
(its why its nice to a have more than one, means you just have a few blocks you can move around :smile: )

yeah the thing is most of the time you want the midi in objects to be 'omni', to react to any channel, and then its the patch that restricts this down... but in the case of the top level patch, in particular, this is not always desirable.
actually I think, once in dev, I did put the channel selector on all in objects, but its was deemed as potentially confusing, a source for user bugs... perhaps we should review this again.

I think I would probably store active notes, i.e. if you have 5 notes held down, you only need an array with 5 elements, so number of channels becomes irrelevant.

this is cool, actually Ive been meaning to just put a controller script together to flash this LED on any midi activity, its so handy knowing when there is midi being received :smile:


#3

Not much to add as i'm with thetechnobear opinion; it works great!
Nice to have along a Controller object? but for me this would have to be addressed to CC number, leaving PGM for patch change :wink: easy to change?

Just end to say you can be part of :
https://sebiik.github.io/community.axoloti.com.backup/c/community-library


#4

I'm not sure I follow you, do you mean that you'd have MIDI reception at a top level, and then transfer the data to subpatches using an internal MIDI port, or something like that?

Perhaps there could be a "global" MIDI channel object for the whole patch, and each MIDI input object could follow the global object, as it is likely that all MIDI input objects would respond to the same channel. If no global object were present, the inputs could default to omni.

As it is right now it is largely confusing, because there's no indication that there's any channel being selected, and it's hard coded to channel 1. Perhaps most people use channel 1 for the outputs from their master keyboards, so it doesn't really matter in most cases?

You don't know beforehand how many simultaneous notes there will be held (although it's unlikely to be many more than the number of fingers for an average person! :smile: in theory it could be the number of available MIDI notes though), so you don't know how large to make the array, and also, that approach means you have to search through the list in order when receiving the note off. If the patch doesn't do much else it would be ok, but I think there's a point to keeping the CPU load as low as possible so I'd like to avoid searches whenever possible.

A relevant question is: roughly how much RAM is available for a patch for this type of data?


#5

no, thats not how it works...

attr_midichannel is replaced when the patch is compiled, and is set to the channel that is set in patch settings. (and 0 can = omni), the messages are then routed directly, no 'internal port'
so the principle is, that it is the patch settings that determine the midi channel in use. (so yes there is an indication of which midi channels, its there!)
this way you can have an different sub patches set for different midi channels e.g. one for lead sound, another for bass another for drums etc.

my point was its could be a bit different in the top level patch , where you might want to deal with this differently, e.g. have some objects that were tied to a particular channel, without having to create a subpatch to do the filtering.

Id not like a global or even patch midi object, too easy for users to miss, and again, doesn't help when you want a single patch to handle multiple channels.

its ok the way it is, I was trying to explain why it is as it is, and that its not hard coded to 1, as you said... and then got a bit side tracked.

touches vs notes, as with all things, you have to make design choices, there are always trade-offs.
was just a suggestions vs. what seemed a pretty large memory usage, which is going to me mainly filled with zeros :wink:
but hey, its your object, so your choice... you decide the 'constraints'.

you have plenty of memory as long as you use sdram tables. but of course the larger you make it, the less likely it is it can be used with other patches.


#6

Ok, thanks for clarifying that, I hadn't really noticed the MIDI channel settings in the settings window. It all makes more sense now, I was otherwise wondering why I didn't get an 'undefined variable' error when attr_midichannel was being referenced in the object but there was no definition for it.

However, I can't seem to set the midi channel to 0 for omni, and as the code is written it tests directly against attr_midichannel with no special case for 0.

Yes, that is really the way I was hoping it would work.

How about two different types if midi/input objects, one with a local channel selection and one using the global one? Or perhaps even better having a channel selector that by default is set to the special value 'patch', where it gets the setting from attr_midichannel. That way if you through up a midi input object, it by default just follows the patch settings, but you can always change it if you want.

I agree, yes it's much better the way it is.

After I wrote my reply I realized that the search would be done when a MIDI event occurs, not at the k-rate code so I guess it would not impact too much on the available DSP performance which was what I was getting at - but of course when something does happen it will take time at that particular point.

I suppose if I just declare an array it goes into the internal RAM of the STM32F427? That's why I was wondering if there's a rough idea of how much is actually available in this application.

Can I set up an array in SDRAM using a C declaration with some qualifier of some sort, or does one need special functions for that?


#7

Sdram 8Mb (is available for user applications)


#8

yes, you can of course see this in the alloc table sdram object

static int16_t _array[attr_poly][LENGTH] __attribute__ ((section (".sdram")));

#9

Ok, here's an updated version, which has a configurable midi/thru object so that it echos other MIDI messages than note on and off. I've also added an omni attribute to the midi/transpose and midi/thru objects so that they either operate on any MIDI channel, or the specific one set for the patch.

When I tried adding parameters I got an 'out of scope' error when referencing them in the MIDI code. What I seemed to need to do is have internal local variables to which I copy the parameter values in the k-rate code, which in turn are accessed by the MIDI code.

I've been toying with the idea of creating 'note on any' and 'note off any' objects, similar to 'cc any', which would allow me to do the MIDI processing in the actual patch and not inside an object. It would incur more latency though, as each step of processing would incur a k-rate sample time delay (which admittedly is only 333µs).


miditranspose.axp (5.6 KB) transpose.axo (2.7 KB) thru.axo (3.6 KB)


Transpose external sequences
#10

Is there a way to convert a signal to program change (like there is with midi CC's) within the axoloti? I need a simple way to shift up and down octaves and I will be using a midi controller that doesn't send program change. Id like to be able to transpose via push buttons into the gpios.

I've thought about adding/subtracting constants (12) to the midi notes using flip/flops, vcas, and mixers, but if there is a more elegant way, I would prefer it.


#11

Im not quite sure what you mean really, you can use midi/in/cc in a same way as the example above instead of midi/in/pgm....

sure you can use gpio/in/digital

really it all comes down to what you want to do exactly, basically in a patch, if the input comes from midi or gpio, it all gets converted into the same data types, from which you can then add your own transpose logic. (which depends on your needs e.g. do you want a simple up/down 1 octave, or to be able to select octaves from a dial/pot etc )


#12

D'oh! Thanks, I'll try it out with midi cc!