Skip to content

Commit 101961c

Browse files
authored
Merge pull request #21 from brummbrum/Automation
Automation implemented
2 parents e8ce290 + 25dc5c6 commit 101961c

File tree

2 files changed

+93
-52
lines changed

2 files changed

+93
-52
lines changed

src/niMidi.cpp

+91-52
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ const unsigned char CMD_METRO = 0x17;
3838
const unsigned char CMD_TEMPO = 0x18;
3939
const unsigned char CMD_UNDO = 0x20;
4040
const unsigned char CMD_REDO = 0x21;
41-
const unsigned char CMD_QUANTIZE = 0x22; // ToDo: ?
42-
const unsigned char CMD_AUTO = 0x23; // ToDo: Toggle between automation modes
41+
const unsigned char CMD_QUANTIZE = 0x22; // ToDo: Input Quantize
42+
const unsigned char CMD_AUTO = 0x23;
4343
const unsigned char CMD_NAV_TRACKS = 0x30;
4444
const unsigned char CMD_NAV_BANKS = 0x31;
4545
const unsigned char CMD_NAV_CLIPS = 0x32;
@@ -94,7 +94,7 @@ const bool HIDE_MUTED_BY_SOLO = false;
9494

9595
// State Information
9696
// ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration
97-
static int g_trackInFocus = 0;
97+
static int g_trackInFocus = -1;
9898
static bool g_anySolo = false;
9999
static int g_soloStateBank[BANK_NUM_TRACKS] = { 0 };
100100
static bool g_muteStateBank[BANK_NUM_TRACKS] = { false };
@@ -108,13 +108,10 @@ signed char convertSignedMidiValue(unsigned char value) {
108108
return value - 128;
109109
}
110110

111-
static unsigned char panToChar(double pan)
112-
{
111+
static unsigned char panToChar(double pan){
113112
pan = (pan + 1.0)*63.5;
114-
115113
if (pan < 0.0)pan = 0.0;
116114
else if (pan > 127.0)pan = 127.0;
117-
118115
return (unsigned char)(pan + 0.5);
119116
}
120117

@@ -203,14 +200,14 @@ class NiMidiSurface: public BaseSurface {
203200
}
204201
}
205202

206-
// ToDo: add more button lights: AUTO, clear, quantize, 4D encoder navigation (blue LEDs)
203+
// ToDo: add button lights for 4D encoder navigation (blue LEDs)
207204

208205
virtual void SetTrackListChange() override {
209206
// If tracklist changes update Mixer View and ensure sanity of track and bank focus
210207
int numTracks = CSurf_NumTracks(false);
211208
// Protect against loosing track focus that could impede track navigation. Set focus on last track in this case.
212209
if (g_trackInFocus > numTracks) {
213-
g_trackInFocus = numTracks;
210+
g_trackInFocus = numTracks;
214211
// Unfortunately we cannot afford to explicitly select the last track automatically because this could screw up
215212
// running actions or macros. The plugin must not manipulate track selection without the user deliberately triggering
216213
// track selection/navigation on the keyboard (or from within Reaper).
@@ -225,51 +222,69 @@ class NiMidiSurface: public BaseSurface {
225222
// the track holding the focused KK instance will also be selected. This situation gets resolved as soon as any form of
226223
// track navigation/selection happens (from keyboard or from within Reaper).
227224
this->_allMixerUpdate();
225+
this->_metronomeUpdate(); // check if metronome status has changed on project tab change
228226
}
229227

228+
// ToDo: SetTrackTitle()
229+
230230
virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override {
231231
// Using SetSurfaceSelected() rather than OnTrackSelection():
232-
// SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks).
232+
// SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks, change of any record arm, change of any auto mode).
233233
// However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15
234+
// Especially record arm leads to a cascade of calls with !selected. It thus requires some event filtering to avoid unecessary CPU and MIDI bandwidth usage
234235

235-
// Note: SetSurfaceSelected is also called on project tab change or when record arming. However, <selected> will only be true if a track selection has changed.
236-
this->_metronomeUpdate(); //check if metronome status has changed when switching project tabs
237-
// Track selection has changed:
238236
if (selected) {
239-
int id = CSurf_TrackToID(track, false);
237+
int id = CSurf_TrackToID(track, false);
240238
int numInBank = id % BANK_NUM_TRACKS;
241-
int oldBankStart = this->_bankStart;
242-
this->_bankStart = id - numInBank;
243-
if (this->_bankStart != oldBankStart) {
244-
// Update everything
245-
this->_allMixerUpdate(); // Note: this will also update the g_muteStateBank and g_soloStateBank caches
246-
}
247-
else {
248-
// Update track names
249-
this->_namesMixerUpdate();
250-
}
251-
// Let Keyboard know about changed track selection
252-
this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank);
253-
g_trackInFocus = id;
254-
if (g_trackInFocus != 0) {
255-
// Mark selected track as available and update Mute and Solo Button lights
256-
this->_sendSysex(CMD_SEL_TRACK_AVAILABLE, 1, 0);
257-
this->_sendSysex(CMD_TOGGLE_SEL_TRACK_MUTE, g_muteStateBank[numInBank] ? 1 : 0, 0);
258-
this->_sendSysex(CMD_TOGGLE_SEL_TRACK_SOLO, g_soloStateBank[numInBank], 0);
259-
if (g_anySolo) {
260-
this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, (g_soloStateBank[numInBank] == 0) ? 1 : 0, 0);
239+
if (id != g_trackInFocus) {
240+
// Track selection has changed
241+
g_trackInFocus = id;
242+
int oldBankStart = this->_bankStart;
243+
this->_bankStart = id - numInBank;
244+
if (this->_bankStart != oldBankStart) {
245+
// Update everything
246+
this->_allMixerUpdate(); // Note: this will also update the g_muteStateBank and g_soloStateBank caches
261247
}
262248
else {
263-
this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, 0, 0);
249+
// Update track names
250+
this->_namesMixerUpdate(); // ToDo: maybe we can scrap this if we implement SetTrackTitle ()
264251
}
252+
if (g_trackInFocus != 0) {
253+
// Mark selected track as available and update Mute and Solo Button lights
254+
this->_sendSysex(CMD_SEL_TRACK_AVAILABLE, 1, 0);
255+
this->_sendSysex(CMD_TOGGLE_SEL_TRACK_MUTE, g_muteStateBank[numInBank] ? 1 : 0, 0);
256+
this->_sendSysex(CMD_TOGGLE_SEL_TRACK_SOLO, g_soloStateBank[numInBank], 0);
257+
if (g_anySolo) {
258+
this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, (g_soloStateBank[numInBank] == 0) ? 1 : 0, 0);
259+
}
260+
else {
261+
this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, 0, 0);
262+
}
263+
}
264+
else {
265+
// Master track not available for Mute and Solo
266+
this->_sendSysex(CMD_SEL_TRACK_AVAILABLE, 0, 0);
267+
}
268+
// Let Keyboard know about changed track selection
269+
this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank);
270+
// Set KK Instance Focus
271+
this->_sendSysex(CMD_SET_KK_INSTANCE, 0, 0, getKkInstanceName(track));
272+
}
273+
// Check automation mode
274+
// AUTO = ON: touch, write, latch or latch preview
275+
// AUTO = OFF: trim or read
276+
int globalAutoMode = GetGlobalAutomationOverride();
277+
if ((id == g_trackInFocus) && (globalAutoMode == -1)) {
278+
// Check automation mode of currently focused track
279+
int autoMode = *(int*)GetSetMediaTrackInfo(track, "I_AUTOMODE", nullptr);
280+
this->_sendCc(CMD_AUTO, autoMode > 1 ? 1 : 0);
265281
}
266282
else {
267-
// Master track not available for Mute and Solo
268-
this->_sendSysex(CMD_SEL_TRACK_AVAILABLE, 0, 0);
283+
// Global Automation Override
284+
this->_sendCc(CMD_AUTO, globalAutoMode > 1 ? 1 : 0);
269285
}
270-
// Set KK Instance Focus
271-
this->_sendSysex(CMD_SET_KK_INSTANCE, 0, 0, getKkInstanceName(track));
272286
}
287+
273288
}
274289

275290
virtual void SetSurfaceVolume(MediaTrack* track, double volume) override {
@@ -354,10 +369,7 @@ class NiMidiSurface: public BaseSurface {
354369
}
355370

356371
virtual void SetSurfaceRecArm(MediaTrack* track, bool armed) override {
357-
// ToDo: ISSUE: it seems (confirmed by forum reports) that record arm in Reaper leads to a cascade of callbacks
358-
// In our case it seems that also the SetSurfaceSelected gets triggered although no track selection change happened
359-
// This may even depend on settings in preferences?
360-
// => Consider filtering this somehow with state variables in SetSurfaceSelected or just live with it...
372+
// Note: record arm also leads to a cascade of SetSurfaceSelected callbacks (-> filtering required)
361373
int id = CSurf_TrackToID(track, false);
362374
if ((id >= this->_bankStart) && (id <= this->_bankEnd)) {
363375
int numInBank = id % BANK_NUM_TRACKS;
@@ -430,6 +442,9 @@ class NiMidiSurface: public BaseSurface {
430442
case CMD_REDO:
431443
Main_OnCommand(40030, 0); // Edit: Redo
432444
break;
445+
case CMD_AUTO:
446+
this->_onSelAutoToggle();
447+
break;
433448
case CMD_NAV_TRACKS:
434449
// Value is -1 or 1.
435450
this->_onTrackNav(convertSignedMidiValue(value));
@@ -648,8 +663,7 @@ class NiMidiSurface: public BaseSurface {
648663
}
649664
this->_sendSysex(CMD_TRACK_NAME, 0, numInBank, name);
650665
}
651-
int selected = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr);
652-
this->_sendSysex(CMD_TRACK_SELECTED, selected, numInBank);
666+
this->_sendSysex(CMD_TRACK_SELECTED, id == g_trackInFocus ? 1 : 0, numInBank);
653667
bool muted = *(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr);
654668
g_muteStateBank[numInBank] = muted;
655669
this->_sendSysex(CMD_TRACK_MUTED, muted ? 1 : 0, numInBank);
@@ -666,6 +680,7 @@ class NiMidiSurface: public BaseSurface {
666680
}
667681
}
668682

683+
// ToDo: maybe this can be scrapped once we implement SetTrackTitle
669684
void _namesMixerUpdate() {
670685
int numInBank = 0;
671686
this->_bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; // avoid ambiguity: track counting always zero based
@@ -708,9 +723,6 @@ class NiMidiSurface: public BaseSurface {
708723
}
709724
MediaTrack* track = CSurf_TrackFromID(id, false);
710725
int iSel = 1; // "Select"
711-
// If we rather wanted to "Toggle" than just "Select" we would use:
712-
// int iSel = 0;
713-
// int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1;
714726
_clearAllSelectedTracks();
715727
GetSetMediaTrackInfo(track, "I_SELECTED", &iSel);
716728
}
@@ -761,7 +773,7 @@ class NiMidiSurface: public BaseSurface {
761773
}
762774
MediaTrack* track = CSurf_TrackFromID(id, false);
763775
int soloState = *(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr);
764-
CSurf_OnSoloChange(track, soloState == 0 ? 1 : 0); // ToDo: Consider settings solo state to 2 (soloed in place)
776+
CSurf_OnSoloChange(track, soloState == 0 ? 1 : 0); // ToDo: Consider settings solo state to 2 (soloed in place)? If at all it this behaviour should be configured with a CONST
765777
}
766778

767779
void _onKnobVolumeChange(unsigned char command, signed char value) {
@@ -825,10 +837,39 @@ class NiMidiSurface: public BaseSurface {
825837
return;
826838
}
827839
int soloState = *(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr);
828-
CSurf_OnSoloChange(track, soloState == 0 ? 1 : 0); // ToDo: Consider settings solo state to 2 (soloed in place)
840+
CSurf_OnSoloChange(track, soloState == 0 ? 1 : 0); // ToDo: Consider settings solo state to 2 (soloed in place)? If at all it this behaviour should be configured with a CONST
829841
}
830842
}
831843

844+
void _onSelAutoToggle() {
845+
if (g_trackInFocus == 0) {
846+
// Special function: If the master track is selected & focused then the AUTO button toggles GlobalAutomationOverride
847+
int globalAutoMode = GetGlobalAutomationOverride();
848+
if (globalAutoMode > 1) {
849+
globalAutoMode = -1; // no global override
850+
}
851+
else {
852+
globalAutoMode = 4; // set global override to latch mode
853+
}
854+
SetGlobalAutomationOverride(globalAutoMode);
855+
}
856+
if (g_trackInFocus > 0) {
857+
// Toggle automation of individual tracks
858+
MediaTrack* track = CSurf_TrackFromID(g_trackInFocus, false);
859+
if (!track) {
860+
return;
861+
}
862+
int autoMode = *(int*)GetSetMediaTrackInfo(track, "I_AUTOMODE", nullptr);
863+
if (autoMode > 1) {
864+
autoMode = 0; // set to trim mode
865+
}
866+
else {
867+
autoMode = 4; // set to latch mode
868+
}
869+
GetSetMediaTrackInfo(track, "I_AUTOMODE", &autoMode);
870+
}
871+
}
872+
832873
void* GetConfigVar(const char* cVar) { // Copyright (c) 2010 and later Tim Payne (SWS), Jeffos
833874
int sztmp;
834875
void* p = NULL;
@@ -848,8 +889,6 @@ class NiMidiSurface: public BaseSurface {
848889
this->_sendCc(CMD_METRO, (*(int*)GetConfigVar("projmetroen") & 1));
849890
}
850891

851-
// ToDo: toggle automation mode
852-
853892
void _sendCc(unsigned char command, unsigned char value) {
854893
if (this->_midiOut) {
855894
this->_midiOut->Send(MIDI_CC, command, value, -1);

src/reaKontrol.h

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
#define REAPERAPI_WANT_CSurf_OnMuteChange
5050
#define REAPERAPI_WANT_CSurf_OnSoloChange
5151
#define REAPERAPI_WANT_GetPlayState
52+
#define REAPERAPI_WANT_GetGlobalAutomationOverride
53+
#define REAPERAPI_WANT_SetGlobalAutomationOverride
5254
#define REAPERAPI_WANT_Track_GetPeakInfo
5355
#define REAPERAPI_WANT_mkvolstr
5456
#define REAPERAPI_WANT_mkpanstr

0 commit comments

Comments
 (0)