Iterator for memory optimisation with midi2 trait

I am working on a crate to implement the opendeck midi controller firmware.

I would like to decouple in the crate the logic to handle user interaction from the final firmware. So the specific firmware implements handling the user inputs and midi output, but the create should implement the opendeck specificiation.

Example: An opendeck button can be configured to send different types of midi messages when the button is activated. The number of messages and the types of the messages are different, depending on the configuration. The configuration of the button is stored in a struct and this part is already solved. Now I am working on implementing the handle function which return the MIDI messages when a user activates the buttons.

In opendeck, there are not only buttons, but also other types of user input (e.g Analog inputs, or Encoders). Each type of input has different configuration options, which are stored in different structs.

What I am struggling is to define a generec handle fn which then returns the the midi messages depending o the structs configuration state.

The current attempt looks like the following:

impl Button {
    pub fn handle<'a>(
        &mut self,
        action: Action,
        buffer: &'a mut [u8],
    ) -> Result<Option<BytesMessage<&'a mut [u8]>>, BufferOverflow> {

        match self.message_type {
            ButtonMessageType::NoteOffOnly => {
                if let Action::Pressed = action {
                    let mut m = NoteOff::try_new_with_buffer(buffer)?;
                    m.set_velocity(u7::new(self.value));
                    m.set_note_number(u7::new(self.midi_id));
                    m.set_channel(self.channel.into_midi());
                    return Ok(Some(m.into()));
                }
                Ok(None)
            }
           // create messages for other messages types
   }
}

But what I want to achieve now is to return an Iterator which will then defer the message creation to the iterator.next() call.

This way we could reuse the same message buffer for each call of the iterator.next() call. I would like to avoid having to create a big buffer which is capable to store all the messages. In the worst case I need to create up to 16 different messages on a single user input.

The midi2 crate wrappes the buffer for the different types of messages it can create by using the try_new_with_buffer function.

I am struggling to define an API which returns an iterator. Is GAT the solution to look into for such a use case? I am still pretty new to Rust and struggling to find the best approach.

The source code I am working on can be found here. Its very much WIP

Thanks in advance for any help or guidance.

You cannot make an Iterator that produces items that reuse the same buffer. An Iterator’s items potentially exist all at once (e.g. using .collect()) and thus there would be conflict over what the buffer contains.

1 Like

Thanks for the answer. Maybe I need to rephrase my question then. I don't need the full Iterator semantics. I just want to defer the message creation for each individual midi message. After the message is sent to the output, the item can be discarded. There is no need to collect all the messages. So maybe the Iterator is the wrong word to use in this context. Just a next() method that takes the buffer and returns the optional next message until no more message is to be sent, should be enough

1 Like

what you described sounds like a LendingIterator or StreamingIterator, which is not supported (e.g. syntax sugar to integrate with for loop etc) in Rust (yet?), and is indeed the main motivating example of the GAT rfc

you can check out the lending-iterator crate for an implementation and very detailed documentation:

Some basic considerations to think about:

In the context of MIDI, are the messages around 3 bytes each? That would mean a total of about 48 bytes in your case.

Since the messages are triggered by a button press, how many presses per second do you expect a user to generate at most? Perhaps around 20 taps per second?

How constrained is your embedded system in terms of memory and processing power?

Taking all this into account, my general question is: Is this level of optimization really necessary?

If alloc() and dealloc() are available in your environment, using something like Vec::with_capacity() might simplify buffer management without much overhead.

In the context of MIDI, are the messages around 3 bytes each? That would mean a total of about 48 bytes in your case.

3 bytes are used as maximum for MIDI 1 ChannelVoice messages. However, the library also sends MMC SysEx messages which are 6 bytes long. (Still not too bad). MIDI2 is on the way, which will further increase the memory footprint. Therefore, if possible I would love to require only the buffer for a single message.

How constrained is your embedded system in terms of memory and processing power?

I am writing a library, not a specific application, therefore I would like not to impose too high resource requirements to that application. But there is always a compromise behind every decision. If this idea leads to very high complexity on the consumer, I can reconsider it. I just thought it would be a relatively simple solution to generate each message one after the other.

You can certainly do that. Something like this: Rust Playground [1]

Your Button::handle() would use it by taking a &mut ButtonEvents in place of &mut [u8] and call it's next() method. Nothing too fancy, just encapsulating iterator state inside this ButtonEvents struct and returning mutable byte slices into the buffer it references.


  1. Be careful! This is a pretty bad example. It's provided only to get the idea across. ↩︎

Thank you very much for your suggestion. There is some additional complexity in the library that I want to develop:

  • The configuration state is stored in the Button struct. The messageType is just one value that defines the output messages. There are also other values that are relevant (e.g. the note value for a NoteOn message). Would I wrap the whole Button struct into the ButtonEvent?
  • One action can result in multiple MIDI message. Therefore the action would also need be wrapped into the ButtonEvent?
  • There are also other types of controllers (e.g. Encoders or Analog inputs). The returned object would need to hold differnt types of state objects.

In the code you linked earlier, it looks like the handle method has access to everything it needs apart from “iterator state”. Which is why I suggested bundling that state with the mutable buffer into ButtonEvents. The rest of the code should already have everything it needs for updating the iterator.

Ah, I may have misinterpreted the use case. If you want a new iterator created by each action, a slightly different interface is needed: It would probably return a new ButtonEvents iterator instead of accepting a reference to one.

Sure, there is no inherent limitation since you are willing to diverge from the std Iterator trait. You can do whatever you want (within reason, avoiding Shared XOR Mutable access violations is usually the main concern).

1 Like

How I solved it for now (until I learn to do it better):

  • wrapped my structs which hold the configuraiton into an iterator kind of object (e.g. the ButtonMessages) binding the lifetime of the iterator to the handle method.
  • the next() fn takes an buffer where the next message is written into binding the lifetime of the message to the next() fn
  • Since there are multiple types of input elements (buttons, analog...) I wrap the iterators into an Messages enum. This enum handles the differnt types of input elements so that the handler can return a common type of iterator which can hold all types of input elements and just delivers messages until there no more messages to send.

This seems to solve my poblem pretty well (as far as I can judge)

Any further feedback is of course welcome.

FWIW, you don't seem to use the index field at all. (rustc/clippy should be giving you a warning for that.)

My feedback is an observation that the unit tests don't appear to call next() more than once. Are you sure it works if there is more than one message to return? Given how this interface is used in the tests, I would be inclined to redesign it so there is not capable to call next() more than once. I'm struggling to provide better feedback because my inclination is in contention with your need to allow it to be called many times.

I'm very curious how it's supposed to work with a sequence of calls, where each caller is allowed to provide a different buffer -- does that change things in any meaningful way? The reason I suggested sending the buffer in the construction phase is to avoid that loophole of potentially invalid use.

Oh sorry, I am still working on the implementation. Thanks for having a look, yes clippy warns me about that. I will let you know once i will use it for a next review

1 Like

The implementation is now more complete. I like the idea of passing the buffer only once into the constructor function.

Do you think thats possible and useful in my case?