77import heronarts .lx .midi .MidiNote ;
88import heronarts .lx .midi .MidiNoteOn ;
99import heronarts .lx .midi .surface .LXMidiSurface ;
10- import heronarts .lx .mixer .LXBus ;
10+ import heronarts .lx .mixer .LXAbstractChannel ;
1111import heronarts .lx .mixer .LXChannel ;
1212import heronarts .lx .parameter .BooleanParameter ;
1313import heronarts .lx .parameter .LXParameterListener ;
14+ import heronarts .lx .pattern .LXPattern ;
1415import java .util .ArrayList ;
16+ import java .util .Arrays ;
1517import java .util .HashMap ;
1618import java .util .List ;
1719import java .util .Map ;
1820import java .util .Map .Entry ;
21+ import titanicsend .pattern .TEPerformancePattern ;
1922
2023@ LXMidiSurface .Name ("Akai APC40 mkII TE" )
2124@ LXMidiSurface .DeviceName ("APC40 mkII" )
@@ -58,9 +61,16 @@ public static void setUserButton(UserButton button, BooleanParameter parameter)
5861
5962 private static final List <APC40Mk2 > surfaces = new ArrayList <APC40Mk2 >();
6063
64+ private boolean shiftOn = false ;
65+
66+ private int [] channelKnobValues = new int [CHANNEL_KNOB_NUM ];
67+
6168 public APC40Mk2 (LX lx , LXMidiInput input , LXMidiOutput output ) {
6269 super (lx , input , output );
6370 surfaces .add (this );
71+
72+ // Initialize channel knob values
73+ Arrays .fill (this .channelKnobValues , 0 );
6474 }
6575
6676 @ Override
@@ -162,6 +172,20 @@ private void sendUserButton(UserButton button, boolean on) {
162172 private boolean noteReceived (MidiNote note , boolean on ) {
163173 int pitch = note .getPitch ();
164174
175+ // Track shift state (temporary until available from upstream)
176+ if (pitch == SHIFT ) {
177+ this .shiftOn = on ;
178+ if (on ) {
179+ // TODO: Send normalized value for selected preset? So far it seems okay...
180+ } else {
181+ // When shift is release, send (restore) channel knob values for pattern browsing
182+ for (int i = 0 ; i < CHANNEL_KNOB_NUM ; i ++) {
183+ sendControlChange (0 , CHANNEL_KNOB + i , this .channelKnobValues [i ]);
184+ }
185+ }
186+ return false ;
187+ }
188+
165189 if (on ) {
166190 for (UserButton userButton : UserButton .values ()) {
167191 if (pitch == userButton .note ) {
@@ -186,23 +210,43 @@ public void noteOnReceived(MidiNoteOn note) {
186210 super .noteOnReceived (note );
187211 }
188212
213+ @ Override
214+ public void noteOffReceived (MidiNote note ) {
215+ if (noteReceived (note , false )) {
216+ return ;
217+ }
218+ super .noteOffReceived (note );
219+ }
220+
189221 @ Override
190222 public void controlChangeReceived (MidiControlChange cc ) {
191223 int number = cc .getCC ();
192224
193225 // TE: Channel knobs set focused pattern, if fader is down.
194226 if (number >= CHANNEL_KNOB && number <= CHANNEL_KNOB_MAX ) {
195- int channel = number - CHANNEL_KNOB ;
196- if (channel < this .lx .engine .mixer .channels .size ()) {
197- LXBus bus = this .lx .engine .mixer .channels .get (channel );
198- if (bus instanceof LXChannel && bus .fader .getValue () == 0 ) {
199- int numPatterns = ((LXChannel ) bus ).patterns .size ();
200- if (numPatterns > 0 ) {
201- double normalized = cc .getNormalized ();
202- // Set active pattern
203- ((LXChannel ) bus ).goPatternIndex ((int ) (normalized * (numPatterns - 1 )));
204- // Alternative for focused pattern
205- // ((LXChannel)bus).focusedPattern.setNormalized(normalized);
227+ int channelIndex = number - CHANNEL_KNOB ;
228+ if (channelIndex < this .lx .engine .mixer .channels .size ()) {
229+ LXAbstractChannel abstractChannel = this .lx .engine .mixer .channels .get (channelIndex );
230+ if (abstractChannel instanceof LXChannel channel && isNotLive (abstractChannel )) {
231+ // Knob corresponds to channel, and channel is not live.
232+ if (this .shiftOn ) {
233+ // Change user preset on selected pattern
234+ LXPattern pattern = channel .getActivePattern ();
235+ if (pattern instanceof TEPerformancePattern tePattern ) {
236+ tePattern .getControls ().getPresetSelectorOffair ().setNormalized (cc .getNormalized ());
237+ }
238+ } else {
239+ // Remember knob position in case shift is pressed
240+ this .channelKnobValues [channelIndex ] = cc .getValue ();
241+ // Change active pattern on the channel
242+ int numPatterns = channel .patterns .size ();
243+ if (numPatterns > 0 ) {
244+ double normalized = cc .getNormalized ();
245+ // Set active pattern
246+ channel .goPatternIndex ((int ) (normalized * (numPatterns - 1 )));
247+ // Alternative for focused pattern
248+ // ((LXChannel)bus).focusedPattern.setNormalized(normalized);
249+ }
206250 }
207251 }
208252 }
@@ -212,6 +256,10 @@ public void controlChangeReceived(MidiControlChange cc) {
212256 super .controlChangeReceived (cc );
213257 }
214258
259+ private boolean isNotLive (LXAbstractChannel abstractChannel ) {
260+ return abstractChannel .fader .getValue () == 0 || !abstractChannel .enabled .isOn ();
261+ }
262+
215263 @ Override
216264 public void dispose () {
217265 if (this .isRegistered ) {
0 commit comments