I have a branch with a working APC40 mk1 mapping. To be clear, I've not used the mapping in any concrete scenario. I'm documenting here the mapping to discuss it with others. It will be sent as a pull request soon enough. From memory, I was facing some MIDI issues that need resolving before upstreaming.
Here is an illustration:
And here is what started as a few paragraphs:
Notes about the mapping:
-
Column layout: pretty expected, each column represents a channel. CH1-CH6 then FX1 and FX2.
-
The annotation on this controller is weird. "Track selection" buttons are connected graphically to "Device control" knobs, not "Track control" knobs.
-
The right-hand side (2x4 knobs) is handled through a concept of menu. It can be split or global. The buttons define the currently selected menu.
- In split, the upper portion (UK1-UK8) is global and the lower (LK1-LK8) is controlling a specific "track", based on the "Track selection" buttons.
- In global, no track is selected and all knobs (UK1-UK8 and LK1-LK8) are global.
- I only have one type of global menu implemented for the moment. Global menu 1 ("Detail view" button) is controlling channels 1-4 filters. See it as rows: UK1-UK4 control channel 1 hpf + sculpt freq + sculpt gain + lpf. UK5-UK8 control channel 2, etc.
- Top menu 1 ("Pan" button) controls all 8 gains.
- Top menu 2 ("Send A" button) shows all 8 rms (so knobs are useless, only their LED rings are used). This could be combined with gain control or such, current menus are mostly for testing and can be changed easily.
- Bottom menu 1 ("Clip/track" button) controls, in order: SendFX1, SendFX2, nil, Rms (display only), HighPass, SculptFrequency, SculptGain, LowPass.
- As said, the exact menus available can be changed easily. This was mostly a reflection on how to structure the right side knobs, which we sadly cannot expose as "one knob per feature" because we don't have enough. I don't think we need many menus. I don't know if the concept of menus specific to a channel is useful.
-
"Track selection" buttons also have a second impact: they define what the "Cue level" encoder does.
- When no track is selected, it controls CueA gain.
- Else, it controls the gain on the selected channel.
- This is nice because it is an encoder and not a fixed amount knob. I implemented a non-linear mapping (
delta^3 / 10 with -64 <= delta <= 63) so slow changes are precise but the gain can be changed quickly. It feels pretty intuitive.
-
Many hardware features are unused: 3 rows of the grid, "Scene launch" buttons, "Clip stop" buttons, "Activation" buttons, "Bank select" arrows, tempo buttons, play/pause/rec buttons, the crossfader. There is also a shift button which could be useful (for menu diving), which I wouldn't mind to avoid.
-
For the moment, features innaccessible are:
- Subfilter: nothing.
- Cue A: we only have send and gain, no mixing control (Mix/Split, Mix balance, Bypass limiter, Pre EQ).
- Cue B: nothing.
-
I'm thinking about ways to represent each channel audio signal. The goal double: to visualise quickly what is playing sound (pre or post fader?) and to give feedback to configure gains. Most probably an LED to indicate ranges of peaks (silent, quiet, normal, saturation).
-
About the RHS knobs: the menu system is in place, thinking needs to be put into what few menus are good to give something useful and intuitive.
-
Another idea: it could be useful to be able to expose some knobs/buttons to external applications. Think controlling whatever is behind the input channel (eg decks). It'd probably make it simpler to handle that at the EQUIS level. Else the full mapping logic should be extracted to another application that then exposes MIDI ports sent to EQUIS with only the information it desires. This would probably be messy to implement, because a lot of state would have to be shared.
Hardware characteristics:
-
All buttons cannot light up in the same way. Right-side buttons are only on/off. Activation, Solo/Cue and Record/Arm buttons have fixed colors (Red, Blue, Green respectively). Other buttons (pads) can be off/green/red/yellow or those colors, but blinking.
-
Knob rings have modes. They can be:
- Off, meaning no LED lights up.
- Single, meaning a single LED moves around based on the knob value.
- Volume, LEDs fill up to the current value.
- Pan, LEDs fill up, centered around the middle.
-
We cannot do whatever we want with knobs. For example, I tried turning them into encoders by making them 50% and at each detected change take the change into account and reset to 50%. This is not smooth and not nice to interact with.
Notes about the implementation:
-
I've pushed all current changes as a single commit, for reference.
-
Mute ("Record/Arm" button) is faked inside the mapping. The logic is not straight forward to keep things in sync: we need to handle volume DSP parameter changes made through other ways. It'd be nicer to implement it globally if this button stays in the mapping.
-
In the mapping, the only thing not implemented is the SendFX1/SendFX2 buttons. I was focused on exploiting the knobs and implementing the menu system for that.
-
I think I'll implement a quality-of-life improvement for menus. Assuming we press channel 1 selection button and keep it pressed. The lower menu for it opens, we can change things. If we change something and then release the button, we should go back to the previous state. The same thing should happen with RHS menu buttons. The goal is to allow quick changes and to avoid having to store the previous state in our mind to the restore it manually.
-
I have been struggling with MIDI controller state management. We need to send a SysEx message at the start to put the controller in the right state and have full control over it. But we do not get JACK MIDI port connection events. Even if we did, I don't think we'd be able to detect controller restarts (that doesn't delete the JACK node + link I think).
There is also an issue where the controller needs to be sent the SysEx message, and we need to wait a bit before sending all button+knob states we want. For the moment this whole procedure is controlled through a state machine. I don't have a way inside EQUIS or the mapping to reset the controller once initialised. I have a script that starts EQUIS and creates the proper links using PipeWire tools so the setup is quick (and this is how I do a reset).
-
MIDI message emitting code: rather than using an event based approach (X button pressed, change states of X knobs and do Y other things), I took a more functional approach. I update the state based on events and then, each time, diff with the previous state with the current one to emit the delta MIDI messages. That means all MIDI emitting code is centralised in one place, and not in each event handler. This removes many annoying bugs and simplifies the code touching state.
It could make sense to make that more generic as I assume this logic could be useful to others. The issue is that it might be hard to make generic as there is a lot of controller specific stuff inside of the diffing code.
I have a branch with a working APC40 mk1 mapping. To be clear, I've not used the mapping in any concrete scenario. I'm documenting here the mapping to discuss it with others. It will be sent as a pull request soon enough. From memory, I was facing some MIDI issues that need resolving before upstreaming.
Here is an illustration:

And here is what started as a few paragraphs:
---
### Notes about the mapping:
- **Column layout:** pretty expected, each column represents a channel. CH1-CH6 then FX1 and FX2.
- **The annotation** on this controller is weird. "Track selection" buttons are connected graphically to "Device control" knobs, not "Track control" knobs.
- **The right-hand side (2x4 knobs)** is handled through a concept of **menu**. It can be **split** or **global**. The buttons define the currently selected menu.
- **In split**, the upper portion (UK1-UK8) is global and the lower (LK1-LK8) is controlling a specific "track", based on the "Track selection" buttons.
- **In global**, no track is selected and all knobs (UK1-UK8 and LK1-LK8) are global.
- I only have one type of global menu implemented for the moment. Global menu 1 ("Detail view" button) is controlling channels 1-4 filters. See it as rows: UK1-UK4 control channel 1 hpf + sculpt freq + sculpt gain + lpf. UK5-UK8 control channel 2, etc.
- Top menu 1 ("Pan" button) controls all 8 gains.
- Top menu 2 ("Send A" button) shows all 8 rms (so knobs are useless, only their LED rings are used). This could be combined with gain control or such, current menus are mostly for testing and can be changed easily.
- Bottom menu 1 ("Clip/track" button) controls, in order: SendFX1, SendFX2, nil, Rms (display only), HighPass, SculptFrequency, SculptGain, LowPass.
- As said, the exact menus available can be changed easily. This was mostly a reflection on how to structure the right side knobs, which we sadly cannot expose as "one knob per feature" because we don't have enough. I don't think we need many menus. I don't know if the concept of menus specific to a channel is useful.
- "Track selection" buttons also have a second impact: they define what **the "Cue level" encoder** does.
- When no track is selected, it controls CueA gain.
- Else, it controls the gain on the selected channel.
- This is nice because it is an encoder and not a fixed amount knob. I implemented a non-linear mapping (`delta^3 / 10` with `-64 <= delta <= 63`) so slow changes are precise but the gain can be changed quickly. It feels pretty intuitive.
- **Many hardware features are unused**: 3 rows of the grid, "Scene launch" buttons, "Clip stop" buttons, "Activation" buttons, "Bank select" arrows, tempo buttons, play/pause/rec buttons, the crossfader. There is also a shift button which could be useful (for menu diving), which I wouldn't mind to avoid.
- For the moment, **features innaccessible are**:
- Subfilter: nothing.
- Cue A: we only have send and gain, no mixing control (Mix/Split, Mix balance, Bypass limiter, Pre EQ).
- Cue B: nothing.
- I'm thinking about **ways to represent each channel audio signal**. The goal double: to visualise quickly what is playing sound (pre or post fader?) and to give feedback to configure gains. Most probably an LED to indicate ranges of peaks (silent, quiet, normal, saturation).
- About the RHS knobs: the menu system is in place, thinking needs to be put into what few menus are good to give something useful and intuitive.
- Another idea: it could be useful to be able to expose some knobs/buttons to external applications. Think controlling whatever is behind the input channel (eg decks). It'd probably make it simpler to handle that at the EQUIS level. Else the full mapping logic should be extracted to another application that then exposes MIDI ports sent to EQUIS with only the information it desires. This would probably be messy to implement, because a lot of state would have to be shared.
---
### Hardware characteristics:
- All buttons cannot light up in the same way. Right-side buttons are only on/off. Activation, Solo/Cue and Record/Arm buttons have fixed colors (Red, Blue, Green respectively). Other buttons (pads) can be off/green/red/yellow or those colors, but blinking.
- Knob rings have modes. They can be:
- Off, meaning no LED lights up.
- Single, meaning a single LED moves around based on the knob value.
- Volume, LEDs fill up to the current value.
- Pan, LEDs fill up, centered around the middle.
- We cannot do whatever we want with knobs. For example, I tried turning them into encoders by making them 50% and at each detected change take the change into account and reset to 50%. This is not smooth and not nice to interact with.
---
### Notes about the implementation:
- I've pushed all current changes as [a single commit](https://codeberg.org/tleb/EQUIS/commit/295d154543611387e47de3eadd6d96c367014708), for reference.
- **Mute ("Record/Arm" button) is faked inside the mapping**. The logic is not straight forward to keep things in sync: we need to handle volume DSP parameter changes made through other ways. It'd be nicer to implement it globally if this button stays in the mapping.
- In the mapping, the only thing **not implemented** is the SendFX1/SendFX2 buttons. I was focused on exploiting the knobs and implementing the menu system for that.
- I think I'll implement a **quality-of-life improvement for menus**. Assuming we press channel 1 selection button and keep it pressed. The lower menu for it opens, we can change things. If we change something and then release the button, we should go back to the previous state. The same thing should happen with RHS menu buttons. The goal is to allow quick changes and to avoid having to store the previous state in our mind to the restore it manually.
- I have been struggling with **MIDI controller state management**. We need to send a SysEx message at the start to put the controller in the right state and have full control over it. But we do not get JACK MIDI port connection events. Even if we did, I don't think we'd be able to detect controller restarts (that doesn't delete the JACK node + link I think).
There is also an issue where the controller needs to be sent the SysEx message, and we need to wait a bit before sending all button+knob states we want. For the moment this whole procedure is controlled through a state machine. I don't have a way inside EQUIS or the mapping to reset the controller once initialised. I have a script that starts EQUIS and creates the proper links using PipeWire tools so the setup is quick (and this is how I do a reset).
- **MIDI message emitting code:** rather than using an event based approach (X button pressed, change states of X knobs and do Y other things), I took a more functional approach. I update the state based on events and then, each time, diff with the previous state with the current one to emit the delta MIDI messages. That means all MIDI emitting code is centralised in one place, and not in each event handler. This removes many annoying bugs and simplifies the code touching state.
It could make sense to make that more generic as I assume this logic could be useful to others. The issue is that it might be hard to make generic as there is a lot of controller specific stuff inside of the diffing code.