I couldn’t stop thinking of the level of flexibility I’d like to see for the button handlers, so I had to write a proposal.
Disclaimer: I’m actually a software engineer with long track records in embedded software, and I’m here on this forum just because I play music as a hobby.
Proposal
So the general (and obvious) idea is: make it possible to assign handlers to various button events. Every playback state has its own set of handlers, so e.g. if the playback is active and the focused track is playing, it’s one set of handlers. If the playback is active but the track is muted, it’s another set of handlers; etc.
Every button could generate multiple kinds of events, and it should be possible to assign one or more handlers to one or more events generated by the same button.
A handler is just an action like “mute”, “unmute”, “start overdub”, etc.
Now, one of the most important questions is how do we define events. The level of flexibility really depends on that one. So, in my opinion, would be really great to describe events with something like that (I’m using C declarations below, just because it’s the easiest way for me to communicate the point, and I believe they should be readable by non-developers as well, to a certain extent. I’m not proposing the exact implementation, I’m just trying to communicate the level of configurability I’d like to see as a user, because those declarations translate very well to the GUI form components which I want to operate)
// Describes the time a button was held down
enum HoldTime {
HOLD_TIME_NONE = 0,
HOLD_TIME_SHORT,
HOLD_TIME_LONG,
HOLD_TIME_EXTRALONG,
HOLD_TIME_ANY,
};
// Describes a basic button event: press, hold or release. The "hold" is only
// generated when the user keeps holding a button for LONG or EXTRALONG.
// This is NOT the final event to which handlers can be assigned; see
// BtnEventComposite below.
//
// The zero value is invalid to avoid defaulting to press in composite
// declarations, for more self-documenting code.
//
// For a short press, the sequence of events would be as follows:
// - BTN_EVENT_PRESS
// - BTN_EVENT_RELEASE (with hold time is set to HOLD_TIME_SHORT)
//
// For a long press, the sequence of events would be as follows:
// - BTN_EVENT_PRESS
// - BTN_EVENT_HOLD (with hold time is set to HOLD_TIME_LONG)
// - BTN_EVENT_RELEASE (with hold time is set to HOLD_TIME_LONG)
//
// For a long press, the sequence of events would be as follows:
// - BTN_EVENT_PRESS
// - BTN_EVENT_HOLD (with hold time is set to HOLD_TIME_LONG)
// - BTN_EVENT_HOLD (with hold time is set to HOLD_TIME_EXTRALONG)
// - BTN_EVENT_RELEASE (with hold time is set to HOLD_TIME_EXTRALONG)
enum BtnEvent {
BTN_EVENT_INVALID = 0,
BTN_EVENT_PRESS,
BTN_EVENT_HOLD,
BTN_EVENT_RELEASE,
};
// BtnEventComposite describes a complete composite button event, to which
// handlers can be assigned, e.g.:
// - Release after the first short button press (single tap)
// - Release after the first long button press (long tap)
// - Second button press, following a previous long tap
// - Second button release, both holds were short (double tap)
// etc
struct BtnEventComposite {
// The last button event
enum BtnEvent event;
// First hold time (if happened already)
enum HoldTime holdTime1;
// Second hold time (if happened already)
enum HoldTime holdTime2;
// Third hold time (if happened already)
enum HoldTime holdTime3;
};
So having those declarations, we could describe various events as follows. Let’s start with the most obvious ones:
// The very first button press
struct BtnEventComposite e = {
.event = BTN_EVENT_PRESS,
};
// Release after the first short button press (single tap)
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_SHORT,
};
// Release after the first long button press (long tap)
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_LONG,
};
// Holding a button for a long time (that's when the current Aeros firmware
// triggers the first Undo action, i.e. undo-last-overdub)
struct BtnEventComposite e = {
.event = BTN_EVENT_HOLD,
.holdTime1 = HOLD_TIME_LONG,
};
// Holding a button for extra long time (that's when the current Aeros firmware
// triggers the second Undo action, i.e. undo the rest of the track)
struct BtnEventComposite e = {
.event = BTN_EVENT_HOLD,
.holdTime1 = HOLD_TIME_EXTRALONG,
};
// Second button release, both holds were short (double tap)
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_SHORT,
.holdTime2 = HOLD_TIME_SHORT,
};
// Third button release, all holds were short (triple tap)
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_SHORT,
.holdTime2 = HOLD_TIME_SHORT,
.holdTime3 = HOLD_TIME_SHORT,
};
But it also allows us to support more peculiar cases:
// Second button press, following a previous short tap
struct BtnEventComposite e = {
.event = BTN_EVENT_PRESS,
.holdTime1 = HOLD_TIME_SHORT,
};
// Second button press, following a previous long tap
struct BtnEventComposite e = {
.event = BTN_EVENT_PRESS,
.holdTime1 = HOLD_TIME_LONG,
};
// Second button release, first hold was short, second was long
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_SHORT,
.holdTime2 = HOLD_TIME_LONG,
};
// Second button release, first hold was short, and we don't care about how long
// was the second one
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_SHORT,
.holdTime2 = HOLD_TIME_ANY,
};
// Second button release, first hold was long, second was short
struct BtnEventComposite e = {
.event = BTN_EVENT_RELEASE,
.holdTime1 = HOLD_TIME_LONG,
.holdTime2 = HOLD_TIME_SHORT,
};
// Third button press, all holds were short, so it's a beginning of triple tap
struct BtnEventComposite e = {
.event = BTN_EVENT_PRESS,
.holdTime1 = HOLD_TIME_SHORT,
.holdTime2 = HOLD_TIME_SHORT,
};
Etc etc. And then, we could make handlers assignable to those events, obvioulsy also depending on the current playback state.
A handler could be:
- Mute a track immediately
- Mute a track in the beginning of the next measure
- Start overdub
- Stop overdub
- Undo last overdub
- Redo last overdub
I’m not saying it’s simple to implement this (the functionality itself, together with a GUI form to configure all that). But, Aeros definitely has full potential to do that, and I believe that the configurability on that level is a good way to unlock that potential.
A few notes:
Possibly transient events and undoability of a handler
Some of those events are potentially transient: e.g. if we have a handler (H1) for “Release after the first short button press (single tap)”, and another handler (H2) for “Second button release, both holds were short (double tap)”, then if we reach the H2, it means H1 has already fired and we want to undo it right now before executing H2. But, not all handlers could be undoable (e.g. the handler “erase all tracks” does not sound undoable). Therefore, the GUI form should not allow adding handlers for events which would make another event with non-undoable handler transient.
So in the example above, if the event “Release after the first short button press (single tap)” has a non-undoable handler “erase all tracks”, it means we shouldn’t be able to add new handlers for events which begin with a short tap. However, events which begin wigh a long tap are still fine.
Let me know what you think guys. Would be especially curious to hear from devs (I hope they read this forum). Does it make sense to you?