From 8258c9f1b04095758e53cbeaa7d68e1f2e9489a2 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 24 Apr 2019 07:06:41 +0200 Subject: [PATCH 01/47] Fix off by one for knobs. The first knob in bank 0 now changes the master track --- src/niMidi.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 6f1570c..b1c3788 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -238,16 +238,20 @@ class NiMidiSurface: public BaseSurface { } void _onKnobVolumeChange(unsigned char command, signed char value) { - int numInBank = command - CMD_KNOB_VOLUME1 + 1; + int numInBank = command - CMD_KNOB_VOLUME1; MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); - if (!track) return; + if (!track) { + return; + } CSurf_SetSurfaceVolume(track, CSurf_OnVolumeChange(track, value * 0.1, true), nullptr); } void _onKnobPanChange(unsigned char command, signed char value) { - int numInBank = command - CMD_KNOB_VOLUME1 + 1; + int numInBank = command - CMD_KNOB_VOLUME1; MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); - if (!track) return; + if (!track) { + return; + } CSurf_SetSurfacePan(track, CSurf_OnPanChange(track, value * 1.0, true), nullptr); } From 04f71c99a67c8083412695b657845ae300ce51e0 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 24 Apr 2019 07:13:59 +0200 Subject: [PATCH 02/47] Do not pause when pressing restart and we are already playing --- src/niMidi.cpp | 6 +++++- src/reaKontrol.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index b1c3788..af2799b 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -129,11 +129,15 @@ class NiMidiSurface: public BaseSurface { this->_protocolVersion = value; break; case CMD_PLAY: + // Toggles between play and pause CSurf_OnPlay(); break; case CMD_RESTART: CSurf_GoStart(); - CSurf_OnPlay(); + if (GetPlayState() & ~1) { + // Only play if current state is not playing + CSurf_OnPlay(); + } break; case CMD_REC: CSurf_OnRecord(); diff --git a/src/reaKontrol.h b/src/reaKontrol.h index ab3b7ca..ed6aed5 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -37,6 +37,7 @@ #define REAPERAPI_WANT_CSurf_SetSurfacePan #define REAPERAPI_WANT_CSurf_OnVolumeChange #define REAPERAPI_WANT_CSurf_OnPanChange +#define REAPERAPI_WANT_GetPlayState #include #include From af9ebd0b41d3892f5abf79a7d9b73d5acfcc0aa5 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 24 Apr 2019 07:19:08 +0200 Subject: [PATCH 03/47] Repeat repeats the whole project --- src/niMidi.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index af2799b..cf673c0 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -145,6 +145,9 @@ class NiMidiSurface: public BaseSurface { case CMD_STOP: CSurf_OnStop(); break; + case CMD_LOOP: + Main_OnCommand(1068, 0); // Transport: Toggle repeat + break; case CMD_METRO: Main_OnCommand(40364, 0); // Options: Toggle metronome break; From 8e434f38fe91aa20fe7442ba57d5291a6ee09c36 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 13 May 2019 00:31:15 -0700 Subject: [PATCH 04/47] Update niMidi.cpp Direct track selection in Mixer view w top row buttons --- src/niMidi.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index cf673c0..b767847 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -177,6 +177,10 @@ class NiMidiSurface: public BaseSurface { case CMD_MOVE_TRANSPORT: CSurf_ScrubAmt(convertSignedMidiValue(value)); break; + case CMD_TRACK_SELECTED: + // Select a track from current bank in Mixer Mode with top row buttons + this->_onTrackSelect(value); + break; case CMD_KNOB_VOLUME1: case CMD_KNOB_VOLUME2: case CMD_KNOB_VOLUME3: @@ -244,6 +248,12 @@ class NiMidiSurface: public BaseSurface { // todo: navigate tracks, navigate banks } + void _onTrackSelect(unsigned char TrackInBank) { + int track = this->_bankStart + TrackInBank; + // MISSING: Check if track actually exists (if this is the last bank) + Main_OnCommand(40938 + track, 0); + } + void _onKnobVolumeChange(unsigned char command, signed char value) { int numInBank = command - CMD_KNOB_VOLUME1; MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); From 6be31250587b0952facb2109978f6322a6e11605 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 13 May 2019 00:54:17 -0700 Subject: [PATCH 05/47] Track navigation in Mixer mode --- src/niMidi.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index b767847..aeca99f 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -108,6 +108,8 @@ class NiMidiSurface: public BaseSurface { int numInBank = id % BANK_NUM_TRACKS; int oldBankStart = this->_bankStart; this->_bankStart = id - numInBank; + // ToDo: calling onBankChange needs to happen more frequently. + // Need a different criteria. Display does not update properly. if (this->_bankStart != oldBankStart) { this->_onBankChange(); } @@ -250,7 +252,8 @@ class NiMidiSurface: public BaseSurface { void _onTrackSelect(unsigned char TrackInBank) { int track = this->_bankStart + TrackInBank; - // MISSING: Check if track actually exists (if this is the last bank) + // ToDo: Check if track actually exists (if this is the last bank) and if track <100 + // Direct track select via Reaper action ends at track 99. Is there a better API call? Main_OnCommand(40938 + track, 0); } From e5b1ef12821a7ed88924a91cd108cc375e5bd10d Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 13 May 2019 13:30:17 -0700 Subject: [PATCH 06/47] Stable Comments for future changes. Branch from here as this modifies architecture an multiple areas --- src/niMidi.cpp | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index aeca99f..3aa66f4 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -107,12 +107,22 @@ class NiMidiSurface: public BaseSurface { int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; int oldBankStart = this->_bankStart; + // ToDo: _bankStart should only be changed automatically if incremental + // track navigation is used => couple this to CMD_NAV_TRACKS. + // _bankStart can be manually switched via buttons, see CMD_NAV_BANKS + // This allows mixer view to focus on a bank while selected track (and plugin view focus) + // is elsewhere. That is a much better behavior than currently. this->_bankStart = id - numInBank; - // ToDo: calling onBankChange needs to happen more frequently. - // Need a different criteria. Display does not update properly. + // ToDo: calling onBankChange needs to happen continuously for display to + // update properly. Not just when bank changes. Update frequency requires + // more thought in order to not be too wasteful with bus resources? Maybe + // each paramter should be evaluated for a change and only updated if changed? + // Meh, it is not so much data after all - maybe just be lazy and always + // update everything in current bank... if (this->_bankStart != oldBankStart) { this->_onBankChange(); } + // ToDo: only update CMD_TRACK_SELECTED if currently selected track is in visible bank this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, getKkInstanceName(track)); @@ -169,6 +179,11 @@ class NiMidiSurface: public BaseSurface { 40286, // Track: Go to previous track 0); break; + // Temporarily disabled until automatic _bankStart is fixed, see above + /* case CMD_NAV_BANKS: + // Value is -1 or 1. + this->_onBankSelect(convertSignedMidiValue(value)); + break; */ case CMD_NAV_CLIPS: // Value is -1 or 1. Main_OnCommand(value == 1 ? @@ -177,7 +192,10 @@ class NiMidiSurface: public BaseSurface { 0); break; case CMD_MOVE_TRANSPORT: - CSurf_ScrubAmt(convertSignedMidiValue(value)); + // ToDo: Scrubbing very slow. Rather than just amplifying this value + // have to evaluate incoming MIDI stream to allow for both fine as well + // coarse scrubbing + CSurf_ScrubAmt(convertSignedMidiValue(value)); break; case CMD_TRACK_SELECTED: // Select a track from current bank in Mixer Mode with top row buttons @@ -250,13 +268,26 @@ class NiMidiSurface: public BaseSurface { // todo: navigate tracks, navigate banks } - void _onTrackSelect(unsigned char TrackInBank) { - int track = this->_bankStart + TrackInBank; - // ToDo: Check if track actually exists (if this is the last bank) and if track <100 - // Direct track select via Reaper action ends at track 99. Is there a better API call? + void _onTrackSelect(unsigned char numInBank) { + int track = this->_bankStart + numInBank; + // Direct track select via Reaper action ends at track no 99. + // Is there a better API call to allow infinite no of tracks? + if ((track < 1) || (track > 99)) { + return; + } Main_OnCommand(40938 + track, 0); } + void _onBankSelect(signed char value) { + // Manually switch the bank in Mixer View + int newBankStart = this->_bankStart + (value * BANK_NUM_TRACKS); + int numTracks = CSurf_NumTracks(false); + if ((newBankStart < 0) || (newBankStart > numTracks)) { + return; + } + this->_bankStart = newBankStart; + } + void _onKnobVolumeChange(unsigned char command, signed char value) { int numInBank = command - CMD_KNOB_VOLUME1; MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); From 4229c69e7eb19368595960b607687d4f3324347e Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 14 May 2019 14:00:19 -0700 Subject: [PATCH 07/47] TrackSelect Direct API calls for track selection rather than Reaper actions to overcome 99 track limit --- src/niMidi.cpp | 62 ++++++++++++++++++++++++++++++++++++------------ src/reaKontrol.h | 1 + 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 3aa66f4..8caa516 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -7,6 +7,9 @@ * License: GNU General Public License version 2.0 */ +#define DEBUG_DIAGNOSTICS +#define BASIC_DIAGNOSTICS + #include #include #include @@ -74,6 +77,8 @@ const unsigned char CMD_TOGGLE_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; +static int g_trackInFocus = 0; // Maybe not the best style to use global variable? + // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. signed char convertSignedMidiValue(unsigned char value) { @@ -107,21 +112,28 @@ class NiMidiSurface: public BaseSurface { int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; int oldBankStart = this->_bankStart; - // ToDo: _bankStart should only be changed automatically if incremental - // track navigation is used => couple this to CMD_NAV_TRACKS. + g_trackInFocus = id; + // ToDo: _bankStart should only be changed automatically if focused track has changed. // _bankStart can be manually switched via buttons, see CMD_NAV_BANKS // This allows mixer view to focus on a bank while selected track (and plugin view focus) - // is elsewhere. That is a much better behavior than currently. this->_bankStart = id - numInBank; // ToDo: calling onBankChange needs to happen continuously for display to - // update properly. Not just when bank changes. Update frequency requires - // more thought in order to not be too wasteful with bus resources? Maybe - // each paramter should be evaluated for a change and only updated if changed? - // Meh, it is not so much data after all - maybe just be lazy and always - // update everything in current bank... + // update properly. Not just when bank changes. + // The bank's status information should probably always be updated completely + // when this hooked SetSurfaceSelected is called? Or can we come up with a better + // update frequency & criteria? E.g. VU meters periodically (@block], track mute, solo etc + // on event xy(?), track selection when SetSurfaceSelected (as it is now) etc. + // Needs more thought and probably additional HOOKS besides MIDI events and selection change! + + + this->_onBankChange(); // just a test to constantly update, rename this later to e.g. _bankUpdate + + /* if (this->_bankStart != oldBankStart) { this->_onBankChange(); } + */ + // ToDo: only update CMD_TRACK_SELECTED if currently selected track is in visible bank this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, @@ -136,6 +148,17 @@ class NiMidiSurface: public BaseSurface { } unsigned char& command = event->midi_message[1]; unsigned char& value = event->midi_message[2]; + +#ifdef DEBUG_DIAGNOSTICS + ostringstream s; + s << "Diagnostic: MIDI " << showbase << hex + << (int)event->midi_message[0] << " " + << (int)event->midi_message[1] << " " + << (int)event->midi_message[2] << " Focus Track" + << g_trackInFocus << endl; + ShowConsoleMsg(s.str().c_str()); +#endif + switch (command) { case CMD_HELLO: this->_protocolVersion = value; @@ -222,12 +245,15 @@ class NiMidiSurface: public BaseSurface { this->_onKnobPanChange(command, convertSignedMidiValue(value)); break; default: +#ifdef BASIC_DIAGNOSTICS ostringstream s; s << "Unhandled MIDI message " << showbase << hex << (int)event->midi_message[0] << " " << (int)event->midi_message[1] << " " - << (int)event->midi_message[2] << endl; + << (int)event->midi_message[2] << " Focus Track" + << g_trackInFocus << endl; ShowConsoleMsg(s.str().c_str()); +#endif break; } } @@ -269,17 +295,23 @@ class NiMidiSurface: public BaseSurface { } void _onTrackSelect(unsigned char numInBank) { - int track = this->_bankStart + numInBank; - // Direct track select via Reaper action ends at track no 99. - // Is there a better API call to allow infinite no of tracks? - if ((track < 1) || (track > 99)) { + int id = this->_bankStart + numInBank; + // ToDo: Now it is Toggle-Select tracks in bank. Change: Should unselect all tracks and select only one + MediaTrack* track = CSurf_TrackFromID(id, false); + int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); + /* ALTERNATIVE behaviour: + // Exclusively select only one track via Reaper action. + // Supports up to 99 tracks + if ((id < 1) || (id > 99)) { return; } - Main_OnCommand(40938 + track, 0); + Main_OnCommand(40938 + id, 0); // Track: Select track xx + */ } void _onBankSelect(signed char value) { - // Manually switch the bank in Mixer View + // Manually switch the bank visible in Mixer View int newBankStart = this->_bankStart + (value * BANK_NUM_TRACKS); int numTracks = CSurf_NumTracks(false); if ((newBankStart < 0) || (newBankStart > numTracks)) { diff --git a/src/reaKontrol.h b/src/reaKontrol.h index ed6aed5..c34681e 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -21,6 +21,7 @@ #define REAPERAPI_WANT_CSurf_NumTracks #define REAPERAPI_WANT_CSurf_TrackToID #define REAPERAPI_WANT_CSurf_TrackFromID +#define REAPERAPI_WANT_CSurf_OnTrackSelection #define REAPERAPI_WANT_GetLastTouchedTrack #define REAPERAPI_WANT_CSurf_OnPlay #define REAPERAPI_WANT_ShowConsoleMsg From 9b5442248504e46a673638e62b5ce05ef33e2ee2 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 14 May 2019 16:57:00 -0700 Subject: [PATCH 08/47] More fixes Track selection working Non existing tracks in last bank are now properly marked as not available --- src/niMidi.cpp | 46 +++++++++++++++++++++++++++++----------------- src/reaKontrol.h | 1 + 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 8caa516..0b12f53 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -125,10 +125,10 @@ class NiMidiSurface: public BaseSurface { // on event xy(?), track selection when SetSurfaceSelected (as it is now) etc. // Needs more thought and probably additional HOOKS besides MIDI events and selection change! - - this->_onBankChange(); // just a test to constantly update, rename this later to e.g. _bankUpdate + // Refresh bank information every time when a track got selected + this->_onBankInfoUpdate(); // renamed from _onBankChange to _onBankInfoUpdate - /* + /* DELETE: this is the old code if (this->_bankStart != oldBankStart) { this->_onBankChange(); } @@ -154,8 +154,9 @@ class NiMidiSurface: public BaseSurface { s << "Diagnostic: MIDI " << showbase << hex << (int)event->midi_message[0] << " " << (int)event->midi_message[1] << " " - << (int)event->midi_message[2] << " Focus Track" - << g_trackInFocus << endl; + << (int)event->midi_message[2] << " Focus Track " + << g_trackInFocus << " Bank Start " + << this->_bankStart << endl; ShowConsoleMsg(s.str().c_str()); #endif @@ -250,8 +251,7 @@ class NiMidiSurface: public BaseSurface { s << "Unhandled MIDI message " << showbase << hex << (int)event->midi_message[0] << " " << (int)event->midi_message[1] << " " - << (int)event->midi_message[2] << " Focus Track" - << g_trackInFocus << endl; + << (int)event->midi_message[2] << endl; ShowConsoleMsg(s.str().c_str()); #endif break; @@ -262,14 +262,19 @@ class NiMidiSurface: public BaseSurface { int _protocolVersion = 0; int _bankStart = -1; - void _onBankChange() { + void _onBankInfoUpdate() { int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS; - int numTracks = CSurf_NumTracks(false); + int numTracks = GetNumTracks(); if (bankEnd > numTracks) { bankEnd = numTracks; + // Mark additional bank tracks as not available + int lastInBank = numTracks % BANK_NUM_TRACKS; + for (int i = 7; i > lastInBank; --i) { + this->_sendSysex(CMD_TRACK_AVAIL, 0, i); + } } - for (int id = this->_bankStart; id < bankEnd; ++id, ++numInBank) { + for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { MediaTrack* track = CSurf_TrackFromID(id, false); if (!track) { break; @@ -291,17 +296,24 @@ class NiMidiSurface: public BaseSurface { this->_sendSysex(CMD_TRACK_NAME, 0, numInBank, name); // todo: level meters, volume, pan } - // todo: navigate tracks, navigate banks + // todo: navigate tracks, navigate banks. NOTE: probably not here + } + + void ClearSelected() { + // Clear all selected tracks. Copyright (c) 2010 and later Tim Payne (SWS) + int iSel = 0; + for (int i = 0; i <= GetNumTracks(); i++) + GetSetMediaTrackInfo(CSurf_TrackFromID(i, false), "I_SELECTED", &iSel); } void _onTrackSelect(unsigned char numInBank) { int id = this->_bankStart + numInBank; - // ToDo: Now it is Toggle-Select tracks in bank. Change: Should unselect all tracks and select only one MediaTrack* track = CSurf_TrackFromID(id, false); - int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; - GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); - /* ALTERNATIVE behaviour: - // Exclusively select only one track via Reaper action. + int iSel = 1; // "Select" + // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; // "Toggle" (instead of "Select") + ClearSelected(); + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); + /* DELETE: PREVIOUS code via Reaper action: // Supports up to 99 tracks if ((id < 1) || (id > 99)) { return; @@ -313,7 +325,7 @@ class NiMidiSurface: public BaseSurface { void _onBankSelect(signed char value) { // Manually switch the bank visible in Mixer View int newBankStart = this->_bankStart + (value * BANK_NUM_TRACKS); - int numTracks = CSurf_NumTracks(false); + int numTracks = GetNumTracks(); if ((newBankStart < 0) || (newBankStart > numTracks)) { return; } diff --git a/src/reaKontrol.h b/src/reaKontrol.h index c34681e..e9a07b1 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -18,6 +18,7 @@ #define REAPERAPI_WANT_GetMIDIOutputName #define REAPERAPI_WANT_CreateMIDIInput #define REAPERAPI_WANT_CreateMIDIOutput +#define REAPERAPI_WANT_GetNumTracks #define REAPERAPI_WANT_CSurf_NumTracks #define REAPERAPI_WANT_CSurf_TrackToID #define REAPERAPI_WANT_CSurf_TrackFromID From 9031be9b92797ec88e17df8c5f0810f2a4147786 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Wed, 15 May 2019 14:40:35 -0700 Subject: [PATCH 09/47] BankSelection Working --- src/niMidi.cpp | 71 ++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 0b12f53..4a3dc5f 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -77,7 +77,7 @@ const unsigned char CMD_TOGGLE_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; -static int g_trackInFocus = 0; // Maybe not the best style to use global variable? +static int g_trackInFocus = 0; // Maybe not the best style to use global variable? Who cares, just used for diagnostics at this point // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -111,30 +111,24 @@ class NiMidiSurface: public BaseSurface { if (selected) { int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; - int oldBankStart = this->_bankStart; - g_trackInFocus = id; - // ToDo: _bankStart should only be changed automatically if focused track has changed. - // _bankStart can be manually switched via buttons, see CMD_NAV_BANKS - // This allows mixer view to focus on a bank while selected track (and plugin view focus) - this->_bankStart = id - numInBank; - // ToDo: calling onBankChange needs to happen continuously for display to - // update properly. Not just when bank changes. - // The bank's status information should probably always be updated completely - // when this hooked SetSurfaceSelected is called? Or can we come up with a better - // update frequency & criteria? E.g. VU meters periodically (@block], track mute, solo etc - // on event xy(?), track selection when SetSurfaceSelected (as it is now) etc. - // Needs more thought and probably additional HOOKS besides MIDI events and selection change! - // Refresh bank information every time when a track got selected - this->_onBankInfoUpdate(); // renamed from _onBankChange to _onBankInfoUpdate + // Just a temporary thingy for diagnsotics + g_trackInFocus = id; - /* DELETE: this is the old code - if (this->_bankStart != oldBankStart) { - this->_onBankChange(); - } - */ + // Update _bankStart + this->_bankStart = id - numInBank; - // ToDo: only update CMD_TRACK_SELECTED if currently selected track is in visible bank + // Refresh bank information every time when a track got selected + this->_MixerUpdate(); // renamed from _onBankChange to _MixerUpdate + + // ToDo: The track status information in a bank shall be updated continuously. + // Need to investigate additional(!) hooks to update Mixer! + // For the time being it is only here, meaning the display will only get updated + // on track selection changes. + // VU meters may require an even more frequent update than justified for the other + // status information. Thus it may require its individual hook (e.g. at every audio block?). + + // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, getKkInstanceName(track)); @@ -202,12 +196,11 @@ class NiMidiSurface: public BaseSurface { 40285 : // Track: Go to next track 40286, // Track: Go to previous track 0); - break; - // Temporarily disabled until automatic _bankStart is fixed, see above - /* case CMD_NAV_BANKS: + break; + case CMD_NAV_BANKS: // Value is -1 or 1. this->_onBankSelect(convertSignedMidiValue(value)); - break; */ + break; case CMD_NAV_CLIPS: // Value is -1 or 1. Main_OnCommand(value == 1 ? @@ -262,10 +255,10 @@ class NiMidiSurface: public BaseSurface { int _protocolVersion = 0; int _bankStart = -1; - void _onBankInfoUpdate() { + void _MixerUpdate() { int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS; - int numTracks = GetNumTracks(); + int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) if (bankEnd > numTracks) { bankEnd = numTracks; // Mark additional bank tracks as not available @@ -302,34 +295,32 @@ class NiMidiSurface: public BaseSurface { void ClearSelected() { // Clear all selected tracks. Copyright (c) 2010 and later Tim Payne (SWS) int iSel = 0; - for (int i = 0; i <= GetNumTracks(); i++) + for (int i = 0; i <= GetNumTracks(); i++) // really ALL tracks, hence no use of CSurf_NumTracks GetSetMediaTrackInfo(CSurf_TrackFromID(i, false), "I_SELECTED", &iSel); } void _onTrackSelect(unsigned char numInBank) { int id = this->_bankStart + numInBank; + if (id > GetNumTracks()) { + return; + } MediaTrack* track = CSurf_TrackFromID(id, false); int iSel = 1; // "Select" - // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; // "Toggle" (instead of "Select") + // If we rather wanted to "Toggle" than just "Select" we would use: + // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; ClearSelected(); - GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); - /* DELETE: PREVIOUS code via Reaper action: - // Supports up to 99 tracks - if ((id < 1) || (id > 99)) { - return; - } - Main_OnCommand(40938 + id, 0); // Track: Select track xx - */ + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } void _onBankSelect(signed char value) { - // Manually switch the bank visible in Mixer View + // Manually switch the bank visible in Mixer View WITHOUT influencing track selection int newBankStart = this->_bankStart + (value * BANK_NUM_TRACKS); - int numTracks = GetNumTracks(); + int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) if ((newBankStart < 0) || (newBankStart > numTracks)) { return; } this->_bankStart = newBankStart; + this->_MixerUpdate(); } void _onKnobVolumeChange(unsigned char command, signed char value) { From 928a00e03ffaa28c1348c2495845cca2c498a069 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Wed, 15 May 2019 21:32:50 -0700 Subject: [PATCH 10/47] Fixed Volume & Pan --- src/niMidi.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 4a3dc5f..512c038 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -76,8 +76,9 @@ const unsigned char CMD_TOGGLE_MUTE = 0x66; const unsigned char CMD_TOGGLE_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; +const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View -static int g_trackInFocus = 0; // Maybe not the best style to use global variable? Who cares, just used for diagnostics at this point +static int g_trackInFocus = 0; // Maybe not the best style to use global variable? // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -126,8 +127,26 @@ class NiMidiSurface: public BaseSurface { // For the time being it is only here, meaning the display will only get updated // on track selection changes. // VU meters may require an even more frequent update than justified for the other - // status information. Thus it may require its individual hook (e.g. at every audio block?). - + // status information. Thus it may require its individual hook (e.g. Run(), see below). + + /* SOME IDEAS for better hooks: + virtual void Run() { } // called 30x/sec or so. + + // these will be called by the host when states change etc + virtual void SetTrackListChange() { } + virtual void SetSurfaceVolume(MediaTrack *trackid, double volume) { } + virtual void SetSurfacePan(MediaTrack *trackid, double pan) { } + virtual void SetSurfaceMute(MediaTrack *trackid, bool mute) { } + virtual void SetSurfaceSelected(MediaTrack *trackid, bool selected) { } + virtual void SetSurfaceSolo(MediaTrack *trackid, bool solo) { } + virtual void SetSurfaceRecArm(MediaTrack *trackid, bool recarm) { } + virtual void SetPlayState(bool play, bool pause, bool rec) { } + virtual void SetRepeatState(bool rep) { } + virtual void SetTrackTitle(MediaTrack *trackid, const char *title) { } + virtual bool GetTouchState(MediaTrack *trackid, int isPan) { return false; } + virtual void SetAutoMode(int mode) { } // automation mode for current track + */ + // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, @@ -272,6 +291,9 @@ class NiMidiSurface: public BaseSurface { if (!track) { break; } + // ToDo: In order to consume less time from Reaper and use less interface bandwidth + // we could react individually to state changes for these track parameters + // rather than polling and updating so much. Need to re-think strategy. this->_sendSysex(CMD_TRACK_AVAIL, TRTYPE_UNSPEC, numInBank); int selected = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr); this->_sendSysex(CMD_TRACK_SELECTED, selected, numInBank); @@ -325,20 +347,22 @@ class NiMidiSurface: public BaseSurface { void _onKnobVolumeChange(unsigned char command, signed char value) { int numInBank = command - CMD_KNOB_VOLUME1; + double dvalue = static_cast(value); MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); if (!track) { return; } - CSurf_SetSurfaceVolume(track, CSurf_OnVolumeChange(track, value * 0.1, true), nullptr); + CSurf_OnVolumeChange(track, dvalue * 0.01, true); } void _onKnobPanChange(unsigned char command, signed char value) { - int numInBank = command - CMD_KNOB_VOLUME1; + int numInBank = command - CMD_KNOB_PAN1; + double dvalue = static_cast(value); MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); if (!track) { return; } - CSurf_SetSurfacePan(track, CSurf_OnPanChange(track, value * 1.0, true), nullptr); + CSurf_OnPanChange(track, dvalue * 0.001, true); } void _sendCc(unsigned char command, unsigned char value) { From d871095f7626cbea725c650d976f7237a32aa7fd Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Thu, 16 May 2019 20:05:47 -0700 Subject: [PATCH 11/47] VU meter tests VU meter data format ok, but: - Need a better hook (update frequency) - VU meter updates not working properly. CMD_SEL_TRACK_PARAMS_CHANGED not fully understood yet --- src/niMidi.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++--- src/reaKontrol.h | 1 + 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 512c038..be02787 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -78,7 +78,7 @@ const unsigned char CMD_TOGGLE_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View -static int g_trackInFocus = 0; // Maybe not the best style to use global variable? +static int g_trackInFocus = 0; // Maybe not the best style to use global variable? Leave for now, eliminate later // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -147,12 +147,21 @@ class NiMidiSurface: public BaseSurface { virtual void SetAutoMode(int mode) { } // automation mode for current track */ + // Here just for testing: + this->_vuMixerUpdate(); + // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, getKkInstanceName(track)); } } + /* + virtual void Run() override { + // VU meters + this->_vuMixerUpdate(); + } + */ protected: void _onMidiEvent(MIDI_event_t* event) override { @@ -276,7 +285,7 @@ class NiMidiSurface: public BaseSurface { void _MixerUpdate() { int numInBank = 0; - int bankEnd = this->_bankStart + BANK_NUM_TRACKS; + int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) if (bankEnd > numTracks) { bankEnd = numTracks; @@ -314,6 +323,61 @@ class NiMidiSurface: public BaseSurface { // todo: navigate tracks, navigate banks. NOTE: probably not here } + void _vuMixerUpdate() { + // VU meters + char vuBank[16]; + int j = 0; + double peakMidiValue = 0; + + int numInBank = 0; + int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; + int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) + if (bankEnd > numTracks) { + bankEnd = numTracks; + } + for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { + MediaTrack* track = CSurf_TrackFromID(id, false); + if (!track) { + break; + } + j = 2 * numInBank; + // ToDo: Will require some log conversion (not just multiply) for KK meter to mirror Reaper meter. + // Code can be cleaned up by putting this into a separate function, leave it for now. + peakMidiValue = 127 * Track_GetPeakInfo(track, 0); // left channel + if (peakMidiValue < 0) { peakMidiValue = 0; } + if (peakMidiValue > 127) { peakMidiValue = 127; } + vuBank[j] = static_cast(peakMidiValue); + peakMidiValue = 127 * Track_GetPeakInfo(track, 1); // right channel + if (peakMidiValue < 0) { peakMidiValue = 0; } + if (peakMidiValue > 127) { peakMidiValue = 127; } + vuBank[j+1] = static_cast(peakMidiValue); + } + this->_sendSysex(CMD_TRACK_VU, 2, 0, vuBank); + this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Not fully working yet!! + +#ifdef DEBUG_DIAGNOSTICS + ostringstream s; + s << "Diagnostic: VU " << showbase << hex + << static_cast(vuBank[0]) << " " + << static_cast(vuBank[1]) << " " + << static_cast(vuBank[2]) << " " + << static_cast(vuBank[3]) << " " + << static_cast(vuBank[4]) << " " + << static_cast(vuBank[5]) << " " + << static_cast(vuBank[6]) << " " + << static_cast(vuBank[7]) << " " + << static_cast(vuBank[8]) << " " + << static_cast(vuBank[9]) << " " + << static_cast(vuBank[10]) << " " + << static_cast(vuBank[11]) << " " + << static_cast(vuBank[12]) << " " + << static_cast(vuBank[13]) << " " + << static_cast(vuBank[14]) << " " + << static_cast(vuBank[15]) << endl; + ShowConsoleMsg(s.str().c_str()); +#endif + } + void ClearSelected() { // Clear all selected tracks. Copyright (c) 2010 and later Tim Payne (SWS) int iSel = 0; @@ -352,7 +416,7 @@ class NiMidiSurface: public BaseSurface { if (!track) { return; } - CSurf_OnVolumeChange(track, dvalue * 0.01, true); + CSurf_OnVolumeChange(track, dvalue * 0.007874, true); // scaling by dividing by 127 (0.007874) } void _onKnobPanChange(unsigned char command, signed char value) { @@ -362,7 +426,7 @@ class NiMidiSurface: public BaseSurface { if (!track) { return; } - CSurf_OnPanChange(track, dvalue * 0.001, true); + CSurf_OnPanChange(track, dvalue * 0.00098425, true); // scaling by dividing by 127*8 (0.00098425) } void _sendCc(unsigned char command, unsigned char value) { diff --git a/src/reaKontrol.h b/src/reaKontrol.h index e9a07b1..26c262b 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -40,6 +40,7 @@ #define REAPERAPI_WANT_CSurf_OnVolumeChange #define REAPERAPI_WANT_CSurf_OnPanChange #define REAPERAPI_WANT_GetPlayState +#define REAPERAPI_WANT_Track_GetPeakInfo #include #include From 50ebd55e9853cf1002e56d77ed678b4c96f55897 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Fri, 17 May 2019 02:40:54 -0700 Subject: [PATCH 12/47] VU meter working VU: - functionality is there! - some minor tweaks like scaling still open - code cleanup required --- src/main.cpp | 1 + src/mcu.cpp | 4 ++ src/niMidi.cpp | 102 +++++++++++++++++------------------------------ src/reaKontrol.h | 1 + 4 files changed, 42 insertions(+), 66 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6bd631c..aa6142a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -145,6 +145,7 @@ BaseSurface::~BaseSurface() { } void BaseSurface::Run() { + this->_vuMixerUpdate(); // TEST if (!this->_midiIn) { return; } diff --git a/src/mcu.cpp b/src/mcu.cpp index 4908a7e..a4415cd 100644 --- a/src/mcu.cpp +++ b/src/mcu.cpp @@ -103,6 +103,10 @@ class McuSurface: public BaseSurface { this->_sendRaw(message); } + void _vuMixerUpdate() override { + // DUMMY. This has to to with the current file structure. Maybe better to move Run() callback into niMidi.cpp and mcu.cpp + } + private: void _sendRaw(const string& message) { // MIDI_event_t includes 4 bytes for the message, but we need more. diff --git a/src/niMidi.cpp b/src/niMidi.cpp index be02787..c15f4f9 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -147,22 +147,13 @@ class NiMidiSurface: public BaseSurface { virtual void SetAutoMode(int mode) { } // automation mode for current track */ - // Here just for testing: - this->_vuMixerUpdate(); - // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, getKkInstanceName(track)); } } - /* - virtual void Run() override { - // VU meters - this->_vuMixerUpdate(); - } - */ - + protected: void _onMidiEvent(MIDI_event_t* event) override { if (event->midi_message[0] != MIDI_CC) { @@ -279,6 +270,40 @@ class NiMidiSurface: public BaseSurface { } } + void _vuMixerUpdate() override { + // VU meters + char vuBank[17]; + int j = 0; + double peakMidiValue = 0; + + int numInBank = 0; + int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; + int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) + if (bankEnd > numTracks) { + bankEnd = numTracks; + } + for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { + MediaTrack* track = CSurf_TrackFromID(id, false); + if (!track) { + break; + } + j = 2 * numInBank; + // ToDo: Will require some log conversion (not just multiply) for KK meter to mirror Reaper meter. + // Code can be cleaned up by putting this into a separate function, leave it for now. + peakMidiValue = 127 * Track_GetPeakInfo(track, 0); // left channel + if (peakMidiValue < 1) { peakMidiValue = 1; } // if 0 then channels further to the right are ignored ! + if (peakMidiValue > 127) { peakMidiValue = 127; } + vuBank[j] = static_cast(peakMidiValue); + peakMidiValue = 127 * Track_GetPeakInfo(track, 1); // right channel + if (peakMidiValue < 1) { peakMidiValue = 1; } // if 0 then channels further to the right are ignored ! + if (peakMidiValue > 127) { peakMidiValue = 127; } + vuBank[j + 1] = static_cast(peakMidiValue); + } + vuBank[16] = 0; // JUST A TEST - it seems a sort of stop bit/byte is needed here + this->_sendSysex(CMD_TRACK_VU, 2, 0, vuBank); + this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? Not fully working yet!! + } + private: int _protocolVersion = 0; int _bankStart = -1; @@ -323,61 +348,6 @@ class NiMidiSurface: public BaseSurface { // todo: navigate tracks, navigate banks. NOTE: probably not here } - void _vuMixerUpdate() { - // VU meters - char vuBank[16]; - int j = 0; - double peakMidiValue = 0; - - int numInBank = 0; - int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; - int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) - if (bankEnd > numTracks) { - bankEnd = numTracks; - } - for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { - MediaTrack* track = CSurf_TrackFromID(id, false); - if (!track) { - break; - } - j = 2 * numInBank; - // ToDo: Will require some log conversion (not just multiply) for KK meter to mirror Reaper meter. - // Code can be cleaned up by putting this into a separate function, leave it for now. - peakMidiValue = 127 * Track_GetPeakInfo(track, 0); // left channel - if (peakMidiValue < 0) { peakMidiValue = 0; } - if (peakMidiValue > 127) { peakMidiValue = 127; } - vuBank[j] = static_cast(peakMidiValue); - peakMidiValue = 127 * Track_GetPeakInfo(track, 1); // right channel - if (peakMidiValue < 0) { peakMidiValue = 0; } - if (peakMidiValue > 127) { peakMidiValue = 127; } - vuBank[j+1] = static_cast(peakMidiValue); - } - this->_sendSysex(CMD_TRACK_VU, 2, 0, vuBank); - this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Not fully working yet!! - -#ifdef DEBUG_DIAGNOSTICS - ostringstream s; - s << "Diagnostic: VU " << showbase << hex - << static_cast(vuBank[0]) << " " - << static_cast(vuBank[1]) << " " - << static_cast(vuBank[2]) << " " - << static_cast(vuBank[3]) << " " - << static_cast(vuBank[4]) << " " - << static_cast(vuBank[5]) << " " - << static_cast(vuBank[6]) << " " - << static_cast(vuBank[7]) << " " - << static_cast(vuBank[8]) << " " - << static_cast(vuBank[9]) << " " - << static_cast(vuBank[10]) << " " - << static_cast(vuBank[11]) << " " - << static_cast(vuBank[12]) << " " - << static_cast(vuBank[13]) << " " - << static_cast(vuBank[14]) << " " - << static_cast(vuBank[15]) << endl; - ShowConsoleMsg(s.str().c_str()); -#endif - } - void ClearSelected() { // Clear all selected tracks. Copyright (c) 2010 and later Tim Payne (SWS) int iSel = 0; @@ -387,7 +357,7 @@ class NiMidiSurface: public BaseSurface { void _onTrackSelect(unsigned char numInBank) { int id = this->_bankStart + numInBank; - if (id > GetNumTracks()) { + if (id > CSurf_NumTracks(false)) { return; } MediaTrack* track = CSurf_TrackFromID(id, false); diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 26c262b..f64c2b1 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -59,6 +59,7 @@ class BaseSurface: public IReaperControlSurface { midi_Input* _midiIn = nullptr; midi_Output* _midiOut = nullptr; virtual void _onMidiEvent(MIDI_event_t* event) = 0; + virtual void _vuMixerUpdate() = 0; }; IReaperControlSurface* createNiMidiSurface(int inDev, int outDev); From 87884f775f04629b3e2e7ed03c94f22b658283f5 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Fri, 17 May 2019 16:25:18 -0700 Subject: [PATCH 13/47] Cleanup1 --- src/niMidi.cpp | 55 +++++++++++++++----------------------------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 512c038..c000946 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -112,41 +112,21 @@ class NiMidiSurface: public BaseSurface { if (selected) { int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; - - // Just a temporary thingy for diagnsotics - g_trackInFocus = id; - - // Update _bankStart + int oldBankStart = this->_bankStart; this->_bankStart = id - numInBank; + if (this->_bankStart != oldBankStart) { + this->_allMixerUpdate(); + } - // Refresh bank information every time when a track got selected - this->_MixerUpdate(); // renamed from _onBankChange to _MixerUpdate - - // ToDo: The track status information in a bank shall be updated continuously. - // Need to investigate additional(!) hooks to update Mixer! - // For the time being it is only here, meaning the display will only get updated - // on track selection changes. - // VU meters may require an even more frequent update than justified for the other - // status information. Thus it may require its individual hook (e.g. Run(), see below). - - /* SOME IDEAS for better hooks: - virtual void Run() { } // called 30x/sec or so. - - // these will be called by the host when states change etc - virtual void SetTrackListChange() { } - virtual void SetSurfaceVolume(MediaTrack *trackid, double volume) { } - virtual void SetSurfacePan(MediaTrack *trackid, double pan) { } - virtual void SetSurfaceMute(MediaTrack *trackid, bool mute) { } - virtual void SetSurfaceSelected(MediaTrack *trackid, bool selected) { } - virtual void SetSurfaceSolo(MediaTrack *trackid, bool solo) { } - virtual void SetSurfaceRecArm(MediaTrack *trackid, bool recarm) { } - virtual void SetPlayState(bool play, bool pause, bool rec) { } - virtual void SetRepeatState(bool rep) { } - virtual void SetTrackTitle(MediaTrack *trackid, const char *title) { } - virtual bool GetTouchState(MediaTrack *trackid, int isPan) { return false; } - virtual void SetAutoMode(int mode) { } // automation mode for current track - */ - + // ==================================================================== + // THIS TEMPORARY BLOCK IS JUST FOR TESTING + // We abuse the SetSurfaceSelected callback to update the entire Mixer + // on every track selection change. This will be removed as soon as the + // proper callbacks per parameter are in place + g_trackInFocus = id; // a temporary thingy for diagnsotics + this->_allMixerUpdate(); + // ==================================================================== + // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, @@ -274,7 +254,7 @@ class NiMidiSurface: public BaseSurface { int _protocolVersion = 0; int _bankStart = -1; - void _MixerUpdate() { + void _allMixerUpdate() { int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS; int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) @@ -291,9 +271,6 @@ class NiMidiSurface: public BaseSurface { if (!track) { break; } - // ToDo: In order to consume less time from Reaper and use less interface bandwidth - // we could react individually to state changes for these track parameters - // rather than polling and updating so much. Need to re-think strategy. this->_sendSysex(CMD_TRACK_AVAIL, TRTYPE_UNSPEC, numInBank); int selected = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr); this->_sendSysex(CMD_TRACK_SELECTED, selected, numInBank); @@ -323,7 +300,7 @@ class NiMidiSurface: public BaseSurface { void _onTrackSelect(unsigned char numInBank) { int id = this->_bankStart + numInBank; - if (id > GetNumTracks()) { + if (id > CSurf_NumTracks(false)) { return; } MediaTrack* track = CSurf_TrackFromID(id, false); @@ -342,7 +319,7 @@ class NiMidiSurface: public BaseSurface { return; } this->_bankStart = newBankStart; - this->_MixerUpdate(); + this->_allMixerUpdate(); } void _onKnobVolumeChange(unsigned char command, signed char value) { From 44cd80b17742cfb39462ef1c595c90b7bc385212 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Fri, 17 May 2019 16:55:35 -0700 Subject: [PATCH 14/47] Cleanup2 --- src/niMidi.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index c000946..7bb13f7 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -256,7 +256,7 @@ class NiMidiSurface: public BaseSurface { void _allMixerUpdate() { int numInBank = 0; - int bankEnd = this->_bankStart + BANK_NUM_TRACKS; + int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; // avoid ambiguity: track counting always zero based int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) if (bankEnd > numTracks) { bankEnd = numTracks; @@ -329,7 +329,7 @@ class NiMidiSurface: public BaseSurface { if (!track) { return; } - CSurf_OnVolumeChange(track, dvalue * 0.01, true); + CSurf_OnVolumeChange(track, dvalue * 0.007874, true); // scaling by dividing by 127 (0.007874) } void _onKnobPanChange(unsigned char command, signed char value) { @@ -339,7 +339,7 @@ class NiMidiSurface: public BaseSurface { if (!track) { return; } - CSurf_OnPanChange(track, dvalue * 0.001, true); + CSurf_OnPanChange(track, dvalue * 0.00098425, true); // scaling by dividing by 127*8 (0.00098425) } void _sendCc(unsigned char command, unsigned char value) { From a4fda2ce8c56115acf022076be3c8d8f1916e259 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sat, 18 May 2019 11:04:47 -0700 Subject: [PATCH 15/47] Add log conversion library functions native conversion taken from Reaper SDK --- include/reaper/WDL/db2val.h | 17 +++++++++++++++ src/niMidi.cpp | 43 ++++++++++++++++++++++++++++++++++++- src/reaKontrol.h | 3 +++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 include/reaper/WDL/db2val.h diff --git a/include/reaper/WDL/db2val.h b/include/reaper/WDL/db2val.h new file mode 100644 index 0000000..d9b89cc --- /dev/null +++ b/include/reaper/WDL/db2val.h @@ -0,0 +1,17 @@ +#ifndef _WDL_DB2VAL_H_ +#define _WDL_DB2VAL_H_ + +#include + +#define TWENTY_OVER_LN10 8.6858896380650365530225783783321 +#define LN10_OVER_TWENTY 0.11512925464970228420089957273422 +#define DB2VAL(x) exp((x)*LN10_OVER_TWENTY) +static inline double VAL2DB(double x) +{ + if (x < 0.0000000298023223876953125) return -150.0; + double v=log(x)*TWENTY_OVER_LN10; + return v<-150.0?-150.0:v; +} + + +#endif \ No newline at end of file diff --git a/src/niMidi.cpp b/src/niMidi.cpp index bff62b5..3cd25bb 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -89,6 +89,46 @@ signed char convertSignedMidiValue(unsigned char value) { return value - 128; } +// The following conversion functions (C) 2006-2008 Cockos Incorporated +// Note: Keep static for now +static double charToVol(unsigned char val) +{ + double pos = ((double)val*1000.0) / 127.0; + pos = SLIDER2DB(pos); + return DB2VAL(pos); + +} + +static unsigned char volToChar(double vol) +{ + double d = (DB2SLIDER(VAL2DB(vol))*127.0 / 1000.0); + if (d < 0.0)d = 0.0; + else if (d > 127.0)d = 127.0; + + return (unsigned char)(d + 0.5); +} + +static double charToPan(unsigned char val) +{ + double pos = ((double)val*1000.0 + 0.5) / 127.0; + + pos = (pos - 500.0) / 500.0; + if (fabs(pos) < 0.08) pos = 0.0; + + return pos; +} + +static unsigned char panToChar(double pan) +{ + pan = (pan + 1.0)*63.5; + + if (pan < 0.0)pan = 0.0; + else if (pan > 127.0)pan = 127.0; + + return (unsigned char)(pan + 0.5); +} +// End of conversion functions (C) 2006-2008 Cockos Incorporated + class NiMidiSurface: public BaseSurface { public: NiMidiSurface(int inDev, int outDev) @@ -252,7 +292,7 @@ class NiMidiSurface: public BaseSurface { void _vuMixerUpdate() override { // VU meters - char vuBank[17]; + char vuBank[17]; int j = 0; double peakMidiValue = 0; @@ -270,6 +310,7 @@ class NiMidiSurface: public BaseSurface { j = 2 * numInBank; // ToDo: Will require some log conversion (not just multiply) for KK meter to mirror Reaper meter. // Code can be cleaned up by putting this into a separate function, leave it for now. + // Also: Avoid any conversion if track volume is silent (save CPU) peakMidiValue = 127 * Track_GetPeakInfo(track, 0); // left channel if (peakMidiValue < 1) { peakMidiValue = 1; } // if 0 then channels further to the right are ignored ! if (peakMidiValue > 127) { peakMidiValue = 127; } diff --git a/src/reaKontrol.h b/src/reaKontrol.h index f64c2b1..37f73b6 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -41,8 +41,11 @@ #define REAPERAPI_WANT_CSurf_OnPanChange #define REAPERAPI_WANT_GetPlayState #define REAPERAPI_WANT_Track_GetPeakInfo +#define REAPERAPI_WANT_DB2SLIDER +#define REAPERAPI_WANT_SLIDER2DB #include #include +#include const std::string getKkInstanceName(MediaTrack* track, bool stripPrefix=false); From 47a503f033597347b6832d332aaf22a0c9a14c3b Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sat, 18 May 2019 14:31:26 -0700 Subject: [PATCH 16/47] Log VU meters calibrated - Calibration also valid for other keyboards than S Mk2? --- src/niMidi.cpp | 59 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 3cd25bb..571822c 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -129,6 +129,15 @@ static unsigned char panToChar(double pan) } // End of conversion functions (C) 2006-2008 Cockos Incorporated +static unsigned char vuPeakToChar(double vol) +{ + double d = (DB2SLIDER(VAL2DB(vol) - 52.0)); // Specific calibration for KK Mk2 + if (d < 0.0)d = 0.0; + else if (d > 127.0)d = 127.0; + + return (unsigned char)(d + 0.5); +} + class NiMidiSurface: public BaseSurface { public: NiMidiSurface(int inDev, int outDev) @@ -291,11 +300,19 @@ class NiMidiSurface: public BaseSurface { } void _vuMixerUpdate() override { - // VU meters - char vuBank[17]; + // VU meters + char vuBank[17]; // INIT??? = { 0 }; int j = 0; - double peakMidiValue = 0; + double peakValue = 0; + //ToDo: Peak levels of muted tracks should NOT be polled at all + //ToDo: Calibration to 0dB. If we calibrate in conversion function (~900.0 vs 1000.0 we are off at low levels) + // maybe calibrate before calling conversion or in first WDL conversion VAL2DB + // TRY FIRST: Use directly VAL2DB conversion + //ToDo: Clean up array Initialization , stop byte, array size, CMD_SEL_TRACK_PARAMS_CHANGED + + vuBank[0] = 1; // there will alway be a master track + vuBank[1] = 1; int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) @@ -308,21 +325,28 @@ class NiMidiSurface: public BaseSurface { break; } j = 2 * numInBank; - // ToDo: Will require some log conversion (not just multiply) for KK meter to mirror Reaper meter. - // Code can be cleaned up by putting this into a separate function, leave it for now. - // Also: Avoid any conversion if track volume is silent (save CPU) - peakMidiValue = 127 * Track_GetPeakInfo(track, 0); // left channel - if (peakMidiValue < 1) { peakMidiValue = 1; } // if 0 then channels further to the right are ignored ! - if (peakMidiValue > 127) { peakMidiValue = 127; } - vuBank[j] = static_cast(peakMidiValue); - peakMidiValue = 127 * Track_GetPeakInfo(track, 1); // right channel - if (peakMidiValue < 1) { peakMidiValue = 1; } // if 0 then channels further to the right are ignored ! - if (peakMidiValue > 127) { peakMidiValue = 127; } - vuBank[j + 1] = static_cast(peakMidiValue); + peakValue = Track_GetPeakInfo(track, 0); // left channel + if (peakValue == 0) { + // No log conversion necessary + vuBank[j] = 1; // if 0 then channels further to the right are ignored by KK display + } + else { + vuBank[j] = vuPeakToChar(peakValue); + if (vuBank[j] == 0) { vuBank[j] = 1; } // if 0 then channels further to the right are ignored by KK display + } + peakValue = Track_GetPeakInfo(track, 1); // right channel + if (peakValue == 0) { + // No log conversion necessary + vuBank[j+1] = 1; // if 0 then channels further to the right are ignored by KK display + } + else { + vuBank[j+1] = vuPeakToChar(peakValue); + if (vuBank[j+1] == 0) { vuBank[j+1] = 1; } // if 0 then channels further to the right are ignored by KK display + } } - vuBank[16] = 0; // JUST A TEST - it seems a sort of stop bit/byte is needed here - this->_sendSysex(CMD_TRACK_VU, 2, 0, vuBank); - this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? Not fully working yet!! + vuBank[16] = 0; // JUST A TEST - it seems a sort of stop bit/byte is needed here. See also initialization above + this->_sendSysex(CMD_TRACK_VU, 2, 2, vuBank); // do the params have anything to do with calibration? + this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? Maybe we have to reference last track in bank to indicate end of updates? } private: @@ -381,6 +405,7 @@ class NiMidiSurface: public BaseSurface { MediaTrack* track = CSurf_TrackFromID(id, false); int iSel = 1; // "Select" // If we rather wanted to "Toggle" than just "Select" we would use: + // int iSel = 0; // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; ClearSelected(); GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); From 77d5f226e33896e45b043b3d7bc7fcf2b0c24af3 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sat, 18 May 2019 14:41:44 -0700 Subject: [PATCH 17/47] fix & comment --- src/niMidi.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 571822c..8b1ebb7 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -132,8 +132,8 @@ static unsigned char panToChar(double pan) static unsigned char vuPeakToChar(double vol) { double d = (DB2SLIDER(VAL2DB(vol) - 52.0)); // Specific calibration for KK Mk2 - if (d < 0.0)d = 0.0; - else if (d > 127.0)d = 127.0; + if (d < 0.0)d = 0.0; // Consider setting it to 0.5 because of KK special interpretation of the 0 + else if (d > 127.0)d = 127.0; // Consider setting it to 126.5 return (unsigned char)(d + 0.5); } @@ -345,7 +345,7 @@ class NiMidiSurface: public BaseSurface { } } vuBank[16] = 0; // JUST A TEST - it seems a sort of stop bit/byte is needed here. See also initialization above - this->_sendSysex(CMD_TRACK_VU, 2, 2, vuBank); // do the params have anything to do with calibration? + this->_sendSysex(CMD_TRACK_VU, 2, 0, vuBank); // do the params have anything to do with calibration? this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? Maybe we have to reference last track in bank to indicate end of updates? } From cf79108b82d894365355604f7bf568f96fc80529 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sun, 19 May 2019 20:24:09 -0700 Subject: [PATCH 18/47] Meter precision calibration finished -Meters working -Precise, non linear calibration for KK Mk2 Display Meters --- src/main.cpp | 2 +- src/mcu.cpp | 4 +- src/niMidi.cpp | 107 +++++++++++++++++++++++++++++------------------ src/reaKontrol.h | 2 +- 4 files changed, 71 insertions(+), 44 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index aa6142a..6708378 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -145,7 +145,7 @@ BaseSurface::~BaseSurface() { } void BaseSurface::Run() { - this->_vuMixerUpdate(); // TEST + this->_peakMixerUpdate(); // ToDo: Maybe update only every 2nd call to save CPU? if (!this->_midiIn) { return; } diff --git a/src/mcu.cpp b/src/mcu.cpp index a4415cd..95d074b 100644 --- a/src/mcu.cpp +++ b/src/mcu.cpp @@ -103,8 +103,8 @@ class McuSurface: public BaseSurface { this->_sendRaw(message); } - void _vuMixerUpdate() override { - // DUMMY. This has to to with the current file structure. Maybe better to move Run() callback into niMidi.cpp and mcu.cpp + void _peakMixerUpdate() override { + // DUMMY. Currently only implemented for niMiDi (Mk2). Maybe better to move Run() callback into niMidi.cpp and mcu.cpp? } private: diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 8b1ebb7..732cd2d 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -91,6 +91,7 @@ signed char convertSignedMidiValue(unsigned char value) { // The following conversion functions (C) 2006-2008 Cockos Incorporated // Note: Keep static for now +// Eliminate those that will eventually not be used (e.g. volToChar) static double charToVol(unsigned char val) { double pos = ((double)val*1000.0) / 127.0; @@ -99,7 +100,7 @@ static double charToVol(unsigned char val) } -static unsigned char volToChar(double vol) +static unsigned char volToChar(double vol) { double d = (DB2SLIDER(VAL2DB(vol))*127.0 / 1000.0); if (d < 0.0)d = 0.0; @@ -129,11 +130,27 @@ static unsigned char panToChar(double pan) } // End of conversion functions (C) 2006-2008 Cockos Incorporated -static unsigned char vuPeakToChar(double vol) +static double KK_Mk2_Display_Transform(double x_in) { - double d = (DB2SLIDER(VAL2DB(vol) - 52.0)); // Specific calibration for KK Mk2 - if (d < 0.0)d = 0.0; // Consider setting it to 0.5 because of KK special interpretation of the 0 - else if (d > 127.0)d = 127.0; // Consider setting it to 126.5 + // Scale from approx -92dB (MIDI value = #1) to +5dB (MIDI value = #127) + // IMPORTANT: KK Mk2 meter scale intervals are NOT linear! + // Midi #107 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB + double temp; + temp = 0.0; + double a = 1.4188776691241778E+00; + double b = 4.1718814064260904E-03; + double c = 1.3365905415325063E+01; + temp = pow(a + b * x_in, c); + return temp; +} + +static unsigned char peakToChar(double vol) +{ + double d = KK_Mk2_Display_Transform(VAL2DB(vol)); // Non-linear calibration to KK Mk2 + // For a faster less CPU intensive calculation: + // double d = (DB2SLIDER(VAL2DB(vol) - 52.0)); // Approximate, linear calibration for KK Mk2 + if (d < 0.0)d = 0.0; + else if (d > 127.0)d = 127.0; return (unsigned char)(d + 0.5); } @@ -240,6 +257,12 @@ class NiMidiSurface: public BaseSurface { break; case CMD_NAV_TRACKS: // Value is -1 or 1. + // ---------------------------- ISSUE -------------------------------------------------------- + // Reaper's built in actions will not reliably got to next/previous track relative to currently SELECTED track! + // Rather, these actions are relative to last TOUCHED track. E.g. if volume fader is changed on a non selected track + // and then we navigate the new track will be next to the track with the volume fader touched + // => Use dedicated track navigation via API, not actions + // ------------------------------------------------------------------------------------------- Main_OnCommand(value == 1 ? 40285 : // Track: Go to next track 40286, // Track: Go to previous track @@ -299,20 +322,14 @@ class NiMidiSurface: public BaseSurface { } } - void _vuMixerUpdate() override { - // VU meters - char vuBank[17]; // INIT??? = { 0 }; - int j = 0; - double peakValue = 0; + void _peakMixerUpdate() override { + // Peak meters. Note: Reaper reports peak, NOT VU - //ToDo: Peak levels of muted tracks should NOT be polled at all - //ToDo: Calibration to 0dB. If we calibrate in conversion function (~900.0 vs 1000.0 we are off at low levels) - // maybe calibrate before calling conversion or in first WDL conversion VAL2DB - // TRY FIRST: Use directly VAL2DB conversion - //ToDo: Clean up array Initialization , stop byte, array size, CMD_SEL_TRACK_PARAMS_CHANGED - - vuBank[0] = 1; // there will alway be a master track - vuBank[1] = 1; + // ToDo: Peak Hold in KK display shall be erased when changing bank or when no signal at all + // ToDo: Explore the necessity of CMD_SEL_TRACK_PARAMS_CHANGED and if last parameter (string) shall be the last track in bank + char peakBank[17] = { 0 }; // For some reason there must be this additional char peakBank[16] and it must be set to 0 + int j = 0; + double peakValue = 0; int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) @@ -325,28 +342,38 @@ class NiMidiSurface: public BaseSurface { break; } j = 2 * numInBank; - peakValue = Track_GetPeakInfo(track, 0); // left channel - if (peakValue == 0) { - // No log conversion necessary - vuBank[j] = 1; // if 0 then channels further to the right are ignored by KK display - } - else { - vuBank[j] = vuPeakToChar(peakValue); - if (vuBank[j] == 0) { vuBank[j] = 1; } // if 0 then channels further to the right are ignored by KK display - } - peakValue = Track_GetPeakInfo(track, 1); // right channel - if (peakValue == 0) { - // No log conversion necessary - vuBank[j+1] = 1; // if 0 then channels further to the right are ignored by KK display + // Muted tracks still report peak levels => ignore these + if (*(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr)) { + peakBank[j] = 1; + peakBank[j+1] = 1; } else { - vuBank[j+1] = vuPeakToChar(peakValue); - if (vuBank[j+1] == 0) { vuBank[j+1] = 1; } // if 0 then channels further to the right are ignored by KK display + peakValue = Track_GetPeakInfo(track, 0); // left channel + if (peakValue < 0.0000000298023223876953125) { + // No log conversion necessary if < -150dB + peakBank[j] = 1; // if 0 then channels further to the right are ignored by KK display + } + else { + peakBank[j] = peakToChar(peakValue); + if (peakBank[j] == 0) { peakBank[j] = 1; } // if 0 then channels further to the right are ignored by KK display + } + peakValue = Track_GetPeakInfo(track, 1); // right channel + if (peakValue < 0.0000000298023223876953125) { + // No log conversion necessary if < -150dB + peakBank[j + 1] = 1; // if 0 then channels further to the right are ignored by KK display + } + else { + peakBank[j + 1] = peakToChar(peakValue); + if (peakBank[j + 1] == 0) { peakBank[j + 1] = 1; } // if 0 then channels further to the right are ignored by KK display + } } - } - vuBank[16] = 0; // JUST A TEST - it seems a sort of stop bit/byte is needed here. See also initialization above - this->_sendSysex(CMD_TRACK_VU, 2, 0, vuBank); // do the params have anything to do with calibration? - this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? Maybe we have to reference last track in bank to indicate end of updates? + } + this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); + /* + this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? + // Maybe we have to reference last track in bank to indicate end of updates? + // Or, if the meter for only one track shall be changed this is indicated here + */ } private: @@ -404,9 +431,9 @@ class NiMidiSurface: public BaseSurface { } MediaTrack* track = CSurf_TrackFromID(id, false); int iSel = 1; // "Select" - // If we rather wanted to "Toggle" than just "Select" we would use: - // int iSel = 0; - // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; + // If we rather wanted to "Toggle" than just "Select" we would use: + // int iSel = 0; + // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; ClearSelected(); GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 37f73b6..90f6ece 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -62,7 +62,7 @@ class BaseSurface: public IReaperControlSurface { midi_Input* _midiIn = nullptr; midi_Output* _midiOut = nullptr; virtual void _onMidiEvent(MIDI_event_t* event) = 0; - virtual void _vuMixerUpdate() = 0; + virtual void _peakMixerUpdate() = 0; }; IReaperControlSurface* createNiMidiSurface(int inDev, int outDev); From 3170cbdfcf6c6f7766617d2998d5a727d36ed031 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sun, 19 May 2019 22:39:08 -0700 Subject: [PATCH 19/47] Update niMidi.cpp --- src/niMidi.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 7bb13f7..be3fced 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -78,7 +78,7 @@ const unsigned char CMD_TOGGLE_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View -static int g_trackInFocus = 0; // Maybe not the best style to use global variable? +static int g_trackInFocus = 0; // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -111,6 +111,7 @@ class NiMidiSurface: public BaseSurface { virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { if (selected) { int id = CSurf_TrackToID(track, false); + g_trackInFocus = id; int numInBank = id % BANK_NUM_TRACKS; int oldBankStart = this->_bankStart; this->_bankStart = id - numInBank; @@ -123,7 +124,6 @@ class NiMidiSurface: public BaseSurface { // We abuse the SetSurfaceSelected callback to update the entire Mixer // on every track selection change. This will be removed as soon as the // proper callbacks per parameter are in place - g_trackInFocus = id; // a temporary thingy for diagnsotics this->_allMixerUpdate(); // ==================================================================== @@ -191,10 +191,7 @@ class NiMidiSurface: public BaseSurface { break; case CMD_NAV_TRACKS: // Value is -1 or 1. - Main_OnCommand(value == 1 ? - 40285 : // Track: Go to next track - 40286, // Track: Go to previous track - 0); + this->_onTrackNav(convertSignedMidiValue(value)); break; case CMD_NAV_BANKS: // Value is -1 or 1. @@ -311,6 +308,19 @@ class NiMidiSurface: public BaseSurface { GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } + void _onTrackNav(signed char value) { + // Move to next/previous track relative to currently focused track = last selected track + if (((g_trackInFocus <= 1) && (value < 0)) || + ((g_trackInFocus >= CSurf_NumTracks(false))) && (value >0 )) { + return; + } + int id = g_trackInFocus + value; + MediaTrack* track = CSurf_TrackFromID(id, false); + int iSel = 1; // "Select" + ClearSelected(); + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); + } + void _onBankSelect(signed char value) { // Manually switch the bank visible in Mixer View WITHOUT influencing track selection int newBankStart = this->_bankStart + (value * BANK_NUM_TRACKS); From b484e004355fcb8633aeb8882c8e2bff51a86f11 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sun, 19 May 2019 23:21:06 -0700 Subject: [PATCH 20/47] Ready for pulling Nav Patch --- src/niMidi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 732cd2d..e50fc74 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -262,6 +262,7 @@ class NiMidiSurface: public BaseSurface { // Rather, these actions are relative to last TOUCHED track. E.g. if volume fader is changed on a non selected track // and then we navigate the new track will be next to the track with the volume fader touched // => Use dedicated track navigation via API, not actions + // OK: Ready for pulling a fix // ------------------------------------------------------------------------------------------- Main_OnCommand(value == 1 ? 40285 : // Track: Go to next track From 0729a3622043248816f71fdbd1d160981d54a4a5 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 20 May 2019 09:48:56 -0700 Subject: [PATCH 21/47] Update Readme --- readme.md | 89 +++++++++++++------------------------------------------ 1 file changed, 21 insertions(+), 68 deletions(-) diff --git a/readme.md b/readme.md index d70bd61..38051b9 100644 --- a/readme.md +++ b/readme.md @@ -1,69 +1,22 @@ # ReaKontrol - -- Author: James Teh <jamie@jantrid.net> & other contributors -- Copyright: 2018-2019 James Teh & other contributors -- License: GNU General Public License version 2.0 - -ReaKontrol is a [REAPER](https://www.reaper.fm/) extension which provides advanced host integration for [Native Instruments Komplete Kontrol keyboards](https://www.native-instruments.com/en/products/komplete/keyboards/). -It currently only runs on Windows and requires REAPER 5.92 or later. - -ReaKontrol supports Komplete Kontrol S-series Mk2, A-series and M-series keyboards. -While some initial work has been done to support S-series Mk1 keyboards, this is not yet functional. - -## Supported Functionality -The following functionality is currently supported: - -- Focus follow; i.e. the Komplete Kontrol instance is switched automatically when a track is selected. -- Transport buttons: Play, Restart, Record, Stop, Metronome, Tempo -- Edit buttons: Undo, Redo -- Track navigation -- Clip navigation: moves between project markers -- Mixer view: volume/pan adjustment with the 8 knobs -- The track name and mute, solo and armed states are displayed as appropriate. - -## Download and Installation -For now, there is no installer. -You can [download the latest build of the extension here](https://osara.reaperaccessibility.com/reaper_kontrol.dll). - -Once downloaded, simply copy the `reaper_kontrol.dll` file you downloaded to the `%appdata%\REAPER\UserPlugins` folder using Windows File Explorer. -You can get to this folder by copying the name above and pasting it into either the Windows Run dialog or the File Explorer address bar. - -You do not need to add a control surface or perform any other configuration in REAPER. -Komplete Kontrol Host integration should work as soon as you start REAPER with a Komplete Kontrol keyboard connected. - -## Reporting Issues -Issues should be reported [on GitHub](https://github.com/jcsteh/reaKontrol/issues). - -## Building -This section is for those interested in building ReaKontrol from source code. - -### Getting the Source Code -The ReaKontrol Git repository is located at https://github.com/jcsteh/reaKontrol.git. -You can clone it with the following command, which will place files in a directory named reaKontrol: - -``` -git clone https://github.com/jcsteh/reaKontrol.git -``` - -### Dependencies -To build ReaKontrol, you will need: - -- Microsoft Visual Studio 2017 Community: - * Visual Studio 2019 is not yet supported. - * [Download Visual Studio 2017 Community](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) - * When installing Visual Studio, you need to enable the following: - - On the Workloads tab, in the Windows group: Desktop development with C++ -- Python, version 2.7: - * This is needed by SCons. - * Python 3 and later are not yet supported. - * [Download Python](https://www.python.org/downloads/) -- [SCons](https://www.scons.org/), version 3.0.4 or later: - * Once Python is installed, you should be able to install SCons by simply running this at the command line: `pip install scons` - -### How to Build -To build ReaKontrol, from a command prompt, simply change to the ReaKontrol checkout directory and run `scons`. -The resulting dll can be found in the `build` directory. - -## Contributors -- James Teh -- Leonard de Ruijter +- Fork of the excellent ReaKontrol repository published by James Teh: https://github.com/jcsteh/reaKontrol +- Author: brumbear@pacificpeaks & other contributors +- Copyright: 2019 and later Pacific Peaks Studio, see individual copyrights in source code +- License: GNU General Public License version 2.0. +- License Notes: As the original work is published under GPLv2 the updated programs are also licensed under GPLv2. May be updated to GPLv3 if copyright holder of original work agrees to update too. + +## Feature Integration +This fork is mainly a development branch aiming to continuously add functionality to ReaKontrol. +- New features will be merged into this fork's master @ https://github.com/brummbrum/reaKontrol +- Testing and calibration is done with NI S-Series Mk2 Komplete Kontrol Keyboard. + +## Upstream Integration +- New features are also offered to the upstream master @ https://github.com/jcsteh/reaKontrol +- Some updated features may or may not be merged with the upstream master due to different priorities +- The sequence in which new features are published may be different between this fork and the upstream master due to different priorities + +## Which fork should you use? +- To the extent that features are integrated into the upstream master it is recommended to stick to the original branch @ https://github.com/jcsteh/reaKontrol + +## Build instructions and additional description +- Please refer to the original branch @ https://github.com/jcsteh/reaKontrol From 24eda7a4498c5d1faa335bbdeba32c55f39c0494 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 20 May 2019 11:27:53 -0700 Subject: [PATCH 22/47] Peak Meters Ready --- src/niMidi.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 0b837b1..9dc6759 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -78,7 +78,14 @@ const unsigned char CMD_TOGGLE_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View +// State Information +// Uses: +// - save CPU and MIDI bus resources by avoiding unnecessary updates or state information calls +// - filtering (e.g. lowpass smoothing) of values +// ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; +static int g_volBank_last[BANK_NUM_TRACKS] = { 0xff }; +static int g_panBank_last[BANK_NUM_TRACKS] = { 0xff }; // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -90,8 +97,7 @@ signed char convertSignedMidiValue(unsigned char value) { } // The following conversion functions (C) 2006-2008 Cockos Incorporated -// Note: Keep static for now -// Eliminate those that will eventually not be used (e.g. volToChar) +// ToDo: Eliminate those that will eventually not be used (e.g. volToChar) static double charToVol(unsigned char val) { double pos = ((double)val*1000.0) / 127.0; @@ -188,8 +194,8 @@ class NiMidiSurface: public BaseSurface { // ==================================================================== // THIS TEMPORARY BLOCK IS JUST FOR TESTING // We abuse the SetSurfaceSelected callback to update the entire Mixer - // on every track selection change. This will be removed as soon as the - // proper callbacks per parameter are in place + // on every track selection change. + // ToDo: Remove this block as soon as the proper callbacks per parameter are in place this->_allMixerUpdate(); // ==================================================================== @@ -316,9 +322,10 @@ class NiMidiSurface: public BaseSurface { void _peakMixerUpdate() override { // Peak meters. Note: Reaper reports peak, NOT VU - // ToDo: Peak Hold in KK display shall be erased when changing bank or when no signal at all - // ToDo: Explore the necessity of CMD_SEL_TRACK_PARAMS_CHANGED and if last parameter (string) shall be the last track in bank - char peakBank[17] = { 0 }; // For some reason there must be this additional char peakBank[16] and it must be set to 0 + // ToDo: Peak Hold in KK display shall be erased when changing bank or when no signal at all. + // ToDo: Explore the effect of sending CMD_SEL_TRACK_PARAMS_CHANGED after sending CMD_TRACK_VU + + char peakBank[(BANK_NUM_TRACKS * 2) + 1] = { 0 }; // There must be an additional char peakBank[16] and it must be set to 0 int j = 0; double peakValue = 0; int numInBank = 0; @@ -359,12 +366,7 @@ class NiMidiSurface: public BaseSurface { } } } - this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); - /* - this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0); // Needed at all? - // Maybe we have to reference last track in bank to indicate end of updates? - // Or, if the meter for only one track shall be changed this is indicated here - */ + this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); } private: From e96e5f9fe483002a28a16cd0a83657adec359149 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 20 May 2019 11:44:34 -0700 Subject: [PATCH 23/47] Peak Meters Calibrated - Stub to isolate this pull request from fork master --- src/niMidi.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 9dc6759..60e6e7c 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -7,7 +7,6 @@ * License: GNU General Public License version 2.0 */ -#define DEBUG_DIAGNOSTICS #define BASIC_DIAGNOSTICS #include @@ -214,17 +213,6 @@ class NiMidiSurface: public BaseSurface { unsigned char& command = event->midi_message[1]; unsigned char& value = event->midi_message[2]; -#ifdef DEBUG_DIAGNOSTICS - ostringstream s; - s << "Diagnostic: MIDI " << showbase << hex - << (int)event->midi_message[0] << " " - << (int)event->midi_message[1] << " " - << (int)event->midi_message[2] << " Focus Track " - << g_trackInFocus << " Bank Start " - << this->_bankStart << endl; - ShowConsoleMsg(s.str().c_str()); -#endif - switch (command) { case CMD_HELLO: this->_protocolVersion = value; From 8844e204a1d7bfbcb1090f9cc674771a81cfa017 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 20 May 2019 12:59:36 -0700 Subject: [PATCH 24/47] Substitute pow() with exp() efficiency improvement --- src/niMidi.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 60e6e7c..321722b 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -142,10 +142,10 @@ static double KK_Mk2_Display_Transform(double x_in) // Midi #107 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB double temp; temp = 0.0; - double a = 1.4188776691241778E+00; - double b = 4.1718814064260904E-03; - double c = 1.3365905415325063E+01; - temp = pow(a + b * x_in, c); + double a = 3.4825646584552920E+15; + double b = -2.4473819019771705E+04; + double c = 7.8669055129218032E+02; + temp = a * exp(b / (x_in + c)); // efficiency improvement over previous use of pow() return temp; } From f9e6eba912e994359778c640f0870f189795cffd Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 20 May 2019 22:28:10 -0700 Subject: [PATCH 25/47] Code optimization (see comments) - Use direct linear logarithmic conversion specifc to NI keyboard meter scaling rather than concatenating a calibration frunction after calling Reaper API standard conversions - Speed improvement - Further meter precision improvement --- src/niMidi.cpp | 56 ++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 321722b..5978577 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -95,6 +95,7 @@ signed char convertSignedMidiValue(unsigned char value) { return value - 128; } +//----------------------------------------------------------------------------------------------------------------------- // The following conversion functions (C) 2006-2008 Cockos Incorporated // ToDo: Eliminate those that will eventually not be used (e.g. volToChar) static double charToVol(unsigned char val) @@ -134,31 +135,30 @@ static unsigned char panToChar(double pan) return (unsigned char)(pan + 0.5); } // End of conversion functions (C) 2006-2008 Cockos Incorporated +//----------------------------------------------------------------------------------------------------------------------- -static double KK_Mk2_Display_Transform(double x_in) +static unsigned char peakToChar_KkMk2(double peak) { - // Scale from approx -92dB (MIDI value = #1) to +5dB (MIDI value = #127) - // IMPORTANT: KK Mk2 meter scale intervals are NOT linear! - // Midi #107 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB - double temp; - temp = 0.0; - double a = 3.4825646584552920E+15; - double b = -2.4473819019771705E+04; - double c = 7.8669055129218032E+02; - temp = a * exp(b / (x_in + c)); // efficiency improvement over previous use of pow() - return temp; + // Direct linear logarithmic conversion to KK Mk2 Meter Scaling. + // Contrary to Reaper Peak Meters the dB interval spacing on KK Mk2 displays is NOT linear. + // It is assumed that other NI Keyboards use the same scaling for the meters. + // Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB + double result = 0.0; + double a = -3.2391538612390192E+01; + double b = 3.0673618643561021E+01; + double c = 8.6720798984917224E+01; + double d = 4.4920143012996103E+00; + if (peak > 0.00398107170553497250770252305088) { + result = a + b * log(c * peak + d); // KK Mk2 display has meaningful resolution only above -48dB + } + else { + result = peak * 4019; // linear approximation below -48dB + } + if (result < 0.5) result = 0.5; // if the returned value is 0 then channels further to the right are ignored by KK display! + else if (result > 126.5) result = 126.5; + return (unsigned char)(result + 0.5); // rounding and make sure that minimum value returned is 1 } -static unsigned char peakToChar(double vol) -{ - double d = KK_Mk2_Display_Transform(VAL2DB(vol)); // Non-linear calibration to KK Mk2 - // For a faster less CPU intensive calculation: - // double d = (DB2SLIDER(VAL2DB(vol) - 52.0)); // Approximate, linear calibration for KK Mk2 - if (d < 0.0)d = 0.0; - else if (d > 127.0)d = 127.0; - - return (unsigned char)(d + 0.5); -} class NiMidiSurface: public BaseSurface { public: @@ -310,10 +310,14 @@ class NiMidiSurface: public BaseSurface { void _peakMixerUpdate() override { // Peak meters. Note: Reaper reports peak, NOT VU - // ToDo: Peak Hold in KK display shall be erased when changing bank or when no signal at all. + // ToDo: Peak Hold in KK display shall be erased immediately when changing bank + // ToDo: Peak Hold in KK display shall be erased after decay time t when track muted or no signal. // ToDo: Explore the effect of sending CMD_SEL_TRACK_PARAMS_CHANGED after sending CMD_TRACK_VU - char peakBank[(BANK_NUM_TRACKS * 2) + 1] = { 0 }; // There must be an additional char peakBank[16] and it must be set to 0 + // Meter information is sent to KK as array (string of chars) for all 16 channels (8 x stereo) of one bank. + // A value of 0 will result in stopping to refresh meters further to right. + // The array needs one additional char at peakBank[16] set to 0 as a "end of string" marker. + char peakBank[(BANK_NUM_TRACKS * 2) + 1] = { 0 }; int j = 0; double peakValue = 0; int numInBank = 0; @@ -340,8 +344,7 @@ class NiMidiSurface: public BaseSurface { peakBank[j] = 1; // if 0 then channels further to the right are ignored by KK display } else { - peakBank[j] = peakToChar(peakValue); - if (peakBank[j] == 0) { peakBank[j] = 1; } // if 0 then channels further to the right are ignored by KK display + peakBank[j] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 } peakValue = Track_GetPeakInfo(track, 1); // right channel if (peakValue < 0.0000000298023223876953125) { @@ -349,8 +352,7 @@ class NiMidiSurface: public BaseSurface { peakBank[j + 1] = 1; // if 0 then channels further to the right are ignored by KK display } else { - peakBank[j + 1] = peakToChar(peakValue); - if (peakBank[j + 1] == 0) { peakBank[j + 1] = 1; } // if 0 then channels further to the right are ignored by KK display + peakBank[j + 1] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 } } } From bbfec199bebc36c1014f872a9b92c8178cc81d1b Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 20 May 2019 23:20:24 -0700 Subject: [PATCH 26/47] Bring back debug diagnostics --- src/niMidi.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 5978577..24e27ec 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -7,6 +7,7 @@ * License: GNU General Public License version 2.0 */ +#define DEBUG_DIAGNOSTICS #define BASIC_DIAGNOSTICS #include @@ -213,6 +214,17 @@ class NiMidiSurface: public BaseSurface { unsigned char& command = event->midi_message[1]; unsigned char& value = event->midi_message[2]; +#ifdef DEBUG_DIAGNOSTICS + ostringstream s; + s << "Diagnostic: MIDI " << showbase << hex + << (int)event->midi_message[0] << " " + << (int)event->midi_message[1] << " " + << (int)event->midi_message[2] << " Focus Track " + << g_trackInFocus << " Bank Start " + << this->_bankStart << endl; + ShowConsoleMsg(s.str().c_str()); +#endif + switch (command) { case CMD_HELLO: this->_protocolVersion = value; From 75630b6bce0bf72339ab53501624680451d42f65 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 21 May 2019 11:29:45 -0700 Subject: [PATCH 27/47] Update Readme --- readme.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 38051b9..5ab50b9 100644 --- a/readme.md +++ b/readme.md @@ -5,18 +5,20 @@ - License: GNU General Public License version 2.0. - License Notes: As the original work is published under GPLv2 the updated programs are also licensed under GPLv2. May be updated to GPLv3 if copyright holder of original work agrees to update too. -## Feature Integration +## Feature Integration & Releases This fork is mainly a development branch aiming to continuously add functionality to ReaKontrol. -- New features will be merged into this fork's master @ https://github.com/brummbrum/reaKontrol +- New features will be merged continously into this fork's master @ https://github.com/brummbrum/reaKontrol - Testing and calibration is done with NI S-Series Mk2 Komplete Kontrol Keyboard. +- When a critcial amount of functionality has been reached there will be releases in a dedicated release branch. +- Binaries are planned to be available for releases (Windows only) -## Upstream Integration -- New features are also offered to the upstream master @ https://github.com/jcsteh/reaKontrol -- Some updated features may or may not be merged with the upstream master due to different priorities -- The sequence in which new features are published may be different between this fork and the upstream master due to different priorities +## Upstream Integration into Parent Repository +- New features are also offered to the upstream repository @ https://github.com/jcsteh/reaKontrol +- Some updated features may or may not be merged with the upstream master due to possibly different priorities +- The sequence in which new features are published may be different between this fork and the upstream repository due to possibly different priorities ## Which fork should you use? -- To the extent that features are integrated into the upstream master it is recommended to stick to the original branch @ https://github.com/jcsteh/reaKontrol +- To the extent that features are integrated into the upstream master it is recommended to stick to the parent repository @ https://github.com/jcsteh/reaKontrol ## Build instructions and additional description -- Please refer to the original branch @ https://github.com/jcsteh/reaKontrol +- Please refer to the parent repository @ https://github.com/jcsteh/reaKontrol From 00e1554a401d1b4bfe487401152d11601c87e343 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 21 May 2019 13:05:57 -0700 Subject: [PATCH 28/47] minus96dB and code simplification - linear approximation f meter between -48dB and -96dB - code simplification in peakMixerUpdate --- src/niMidi.cpp | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 5978577..b690bf4 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -137,25 +137,31 @@ static unsigned char panToChar(double pan) // End of conversion functions (C) 2006-2008 Cockos Incorporated //----------------------------------------------------------------------------------------------------------------------- -static unsigned char peakToChar_KkMk2(double peak) -{ +static unsigned char peakToChar_KkMk2(double peak) { // Direct linear logarithmic conversion to KK Mk2 Meter Scaling. // Contrary to Reaper Peak Meters the dB interval spacing on KK Mk2 displays is NOT linear. // It is assumed that other NI Keyboards use the same scaling for the meters. - // Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB + // Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB, #2 = -96dB, #1 = -infinite + + constexpr double minus48dB = 0.00398107170553497250; + constexpr double minus96dB = 1.5848931924611134E-05; + constexpr double m = (16.0 - 2.0) / (minus48dB - minus96dB); + constexpr double n = 16.0 - m * minus48dB; + constexpr double a = -3.2391538612390192E+01; + constexpr double b = 3.0673618643561021E+01; + constexpr double c = 8.6720798984917224E+01; + constexpr double d = 4.4920143012996103E+00; double result = 0.0; - double a = -3.2391538612390192E+01; - double b = 3.0673618643561021E+01; - double c = 8.6720798984917224E+01; - double d = 4.4920143012996103E+00; - if (peak > 0.00398107170553497250770252305088) { + if (peak > minus48dB) { result = a + b * log(c * peak + d); // KK Mk2 display has meaningful resolution only above -48dB } + else if (peak > minus96dB) { + result = m * peak + n; // linear approximation between -96dB and -48dB + } else { - result = peak * 4019; // linear approximation below -48dB + result = 0.5; // will be rounded to 1 } - if (result < 0.5) result = 0.5; // if the returned value is 0 then channels further to the right are ignored by KK display! - else if (result > 126.5) result = 126.5; + if (result > 126.5) result = 126.5; return (unsigned char)(result + 0.5); // rounding and make sure that minimum value returned is 1 } @@ -339,21 +345,9 @@ class NiMidiSurface: public BaseSurface { } else { peakValue = Track_GetPeakInfo(track, 0); // left channel - if (peakValue < 0.0000000298023223876953125) { - // No log conversion necessary if < -150dB - peakBank[j] = 1; // if 0 then channels further to the right are ignored by KK display - } - else { - peakBank[j] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 - } + peakBank[j] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 peakValue = Track_GetPeakInfo(track, 1); // right channel - if (peakValue < 0.0000000298023223876953125) { - // No log conversion necessary if < -150dB - peakBank[j + 1] = 1; // if 0 then channels further to the right are ignored by KK display - } - else { - peakBank[j + 1] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 - } + peakBank[j + 1] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 } } this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); From 27af86ea0d16aa9f9dd2f8c9fb4bb68337684d71 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 21 May 2019 20:37:56 -0700 Subject: [PATCH 29/47] Sceleton for Volume Text - incl editiorial changes (comments, function names, variable names) for a clearer understanding --- src/niMidi.cpp | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 5503ecc..4c7f916 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -50,8 +50,8 @@ const unsigned char CMD_TRACK_SELECTED = 0x42; const unsigned char CMD_TRACK_MUTED = 0x43; const unsigned char CMD_TRACK_SOLOED = 0x44; const unsigned char CMD_TRACK_ARMED = 0x45; -const unsigned char CMD_TRACK_VOLUME_CHANGED = 0x46; -const unsigned char CMD_TRACK_PAN_CHANGED = 0x47; +const unsigned char CMD_TRACK_VOLUME_TEXT = 0x46; +const unsigned char CMD_TRACK_PAN_TEXT = 0x47; const unsigned char CMD_TRACK_NAME = 0x48; const unsigned char CMD_TRACK_VU = 0x49; const unsigned char CMD_KNOB_VOLUME1 = 0x50; @@ -96,6 +96,12 @@ signed char convertSignedMidiValue(unsigned char value) { return value - 128; } +// Convert signed char into decimal ASCII string +char* signedCharToAsciiString(signed char) { + static char s[5]; + return s; +} + //----------------------------------------------------------------------------------------------------------------------- // The following conversion functions (C) 2006-2008 Cockos Incorporated // ToDo: Eliminate those that will eventually not be used (e.g. volToChar) @@ -138,9 +144,9 @@ static unsigned char panToChar(double pan) // End of conversion functions (C) 2006-2008 Cockos Incorporated //----------------------------------------------------------------------------------------------------------------------- -static unsigned char peakToChar_KkMk2(double peak) { +static unsigned char volToChar_KkMk2(double volume) { // Direct linear logarithmic conversion to KK Mk2 Meter Scaling. - // Contrary to Reaper Peak Meters the dB interval spacing on KK Mk2 displays is NOT linear. + // Contrary to Reaper's Peak Volume Meters the dB interval spacing on KK Mk2 displays is NOT linear. // It is assumed that other NI Keyboards use the same scaling for the meters. // Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB, #2 = -96dB, #1 = -infinite @@ -153,11 +159,11 @@ static unsigned char peakToChar_KkMk2(double peak) { constexpr double c = 8.6720798984917224E+01; constexpr double d = 4.4920143012996103E+00; double result = 0.0; - if (peak > minus48dB) { - result = a + b * log(c * peak + d); // KK Mk2 display has meaningful resolution only above -48dB + if (volume > minus48dB) { + result = a + b * log(c * volume + d); // KK Mk2 display has meaningful resolution only above -48dB } - else if (peak > minus96dB) { - result = m * peak + n; // linear approximation between -96dB and -48dB + else if (volume > minus96dB) { + result = m * volume + n; // linear approximation between -96dB and -48dB } else { result = 0.5; // will be rounded to 1 @@ -186,6 +192,8 @@ class NiMidiSurface: public BaseSurface { return "Komplete Kontrol S-series Mk2/A-series/M-series"; } + // ToDo: If the tracklist changes, we always need to call _allMixerUpdate because the change may (or may not) affect the currently visible bank + virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { if (selected) { int id = CSurf_TrackToID(track, false); @@ -333,9 +341,10 @@ class NiMidiSurface: public BaseSurface { // ToDo: Explore the effect of sending CMD_SEL_TRACK_PARAMS_CHANGED after sending CMD_TRACK_VU // Meter information is sent to KK as array (string of chars) for all 16 channels (8 x stereo) of one bank. - // A value of 0 will result in stopping to refresh meters further to right. - // The array needs one additional char at peakBank[16] set to 0 as a "end of string" marker. + // A value of 0 will result in stopping to refresh meters further to right as it is interpretated as "end of string". + // The array needs one additional char at peakBank[16] set as "end of string" marker. char peakBank[(BANK_NUM_TRACKS * 2) + 1] = { 0 }; + peakBank[16] = '\0'; int j = 0; double peakValue = 0; int numInBank = 0; @@ -343,6 +352,8 @@ class NiMidiSurface: public BaseSurface { int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) if (bankEnd > numTracks) { bankEnd = numTracks; + int lastInBank = numTracks % BANK_NUM_TRACKS; + peakBank[(lastInBank +1) * 2] = '\0'; // end of string (no tracks available further to the right) } for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { MediaTrack* track = CSurf_TrackFromID(id, false); @@ -357,11 +368,12 @@ class NiMidiSurface: public BaseSurface { } else { peakValue = Track_GetPeakInfo(track, 0); // left channel - peakBank[j] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 + peakBank[j] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 peakValue = Track_GetPeakInfo(track, 1); // right channel - peakBank[j + 1] = peakToChar_KkMk2(peakValue); // returns value between 1 and 127 + peakBank[j + 1] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 } } + peakBank[j + 2] = '\0'; this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); } @@ -395,7 +407,19 @@ class NiMidiSurface: public BaseSurface { this->_sendSysex(CMD_TRACK_MUTED, muted ? 1 : 0, numInBank); int armed = *(int*)GetSetMediaTrackInfo(track, "I_RECARM", nullptr); this->_sendSysex(CMD_TRACK_ARMED, armed, numInBank); - // todo: volume text, pan text + + //------------------------------------------- + // ToDo: Update Volume text and Volume marker + double volume = *(double*)GetSetMediaTrackInfo(track, "D_VOL", nullptr); + //char vol[2] = { volToChar_KkMk2(volume), 0 }; + //if (vol[0] != g_volBank_last[numInBank]) { + // this->_sendSysex(CMD_TRACK_VOLUME_CHANGED, 0, numInBank, vol); + // g_volBank_last[numInBank] = vol[0]; + //} + //------------------------------------------- + // ToDo: Update Pan text and Pan marker + //------------------------------------------- + char* name = (char*)GetSetMediaTrackInfo(track, "P_NAME", nullptr); if (!name) { name = ""; From 23dfe72d0ac91b9342c8e1f3aa04d5a141fabee6 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Wed, 22 May 2019 01:06:08 -0700 Subject: [PATCH 30/47] Volume Text and Marker working --- src/niMidi.cpp | 94 +++++++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 4c7f916..435f322 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -54,22 +54,22 @@ const unsigned char CMD_TRACK_VOLUME_TEXT = 0x46; const unsigned char CMD_TRACK_PAN_TEXT = 0x47; const unsigned char CMD_TRACK_NAME = 0x48; const unsigned char CMD_TRACK_VU = 0x49; -const unsigned char CMD_KNOB_VOLUME1 = 0x50; -const unsigned char CMD_KNOB_VOLUME2 = 0x51; -const unsigned char CMD_KNOB_VOLUME3 = 0x52; -const unsigned char CMD_KNOB_VOLUME4 = 0x53; -const unsigned char CMD_KNOB_VOLUME5 = 0x54; -const unsigned char CMD_KNOB_VOLUME6 = 0x55; -const unsigned char CMD_KNOB_VOLUME7 = 0x56; -const unsigned char CMD_KNOB_VOLUME8 = 0x57; -const unsigned char CMD_KNOB_PAN1 = 0x58; -const unsigned char CMD_KNOB_PAN2 = 0x59; -const unsigned char CMD_KNOB_PAN3 = 0x5a; -const unsigned char CMD_KNOB_PAN4 = 0x5b; -const unsigned char CMD_KNOB_PAN5 = 0x5c; -const unsigned char CMD_KNOB_PAN6 = 0x5d; -const unsigned char CMD_KNOB_PAN7 = 0x5e; -const unsigned char CMD_KNOB_PAN8 = 0x5f; +const unsigned char CMD_KNOB_VOLUME0 = 0x50; +const unsigned char CMD_KNOB_VOLUME1 = 0x51; +const unsigned char CMD_KNOB_VOLUME2 = 0x52; +const unsigned char CMD_KNOB_VOLUME3 = 0x53; +const unsigned char CMD_KNOB_VOLUME4 = 0x54; +const unsigned char CMD_KNOB_VOLUME5 = 0x55; +const unsigned char CMD_KNOB_VOLUME6 = 0x56; +const unsigned char CMD_KNOB_VOLUME7 = 0x57; +const unsigned char CMD_KNOB_PAN0 = 0x58; +const unsigned char CMD_KNOB_PAN1 = 0x59; +const unsigned char CMD_KNOB_PAN2 = 0x5a; +const unsigned char CMD_KNOB_PAN3 = 0x5b; +const unsigned char CMD_KNOB_PAN4 = 0x5c; +const unsigned char CMD_KNOB_PAN5 = 0x5d; +const unsigned char CMD_KNOB_PAN6 = 0x5e; +const unsigned char CMD_KNOB_PAN7 = 0x5f; const unsigned char CMD_CHANGE_VOLUME = 0x64; const unsigned char CMD_CHANGE_PAN = 0x65; const unsigned char CMD_TOGGLE_MUTE = 0x66; @@ -84,8 +84,8 @@ const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track // - filtering (e.g. lowpass smoothing) of values // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; -static int g_volBank_last[BANK_NUM_TRACKS] = { 0xff }; -static int g_panBank_last[BANK_NUM_TRACKS] = { 0xff }; +// static int g_volBank_last[BANK_NUM_TRACKS] = { 0xff }; +// static int g_panBank_last[BANK_NUM_TRACKS] = { 0xff }; // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -96,9 +96,38 @@ signed char convertSignedMidiValue(unsigned char value) { return value - 128; } -// Convert signed char into decimal ASCII string -char* signedCharToAsciiString(signed char) { - static char s[5]; +// Convert dB value to decimal ASCII string +char* dbToAsciiStringDB(double val) { + char ascii[10] = { '0','1','2','3','4','5','6','7','8','9' }; + static char s[9] = { ' ',' ', ' ', ' ' , '.' , ' ' , 'd' , 'B' , '\0' }; + int db_10 = (int)(val * 10.0); + + if (db_10 < 0) { + s[0] = '-'; + db_10 = -(db_10); + } + else { s[0] = ' '; } + + int c1000 = db_10 / 1000; int m1000 = c1000 * 1000; + int c100 = (db_10 - m1000) / 100; int m100 = c100 * 100; + int c10 = (db_10 - m1000 - m100) / 10; int m10 = c10 * 10; + int c1 = db_10 - m1000 - m100 - m10; + + if (c1000) { + s[1] = ascii[c1000]; + s[2] = ascii[c100]; + } + else { + s[1] = ' '; + if (c100) { + s[2] = ascii[c100]; + } + else { + s[2] = ' '; + } + } + s[3] = ascii[c10]; + s[5] = ascii[c1]; return s; } @@ -300,24 +329,24 @@ class NiMidiSurface: public BaseSurface { // Select a track from current bank in Mixer Mode with top row buttons this->_onTrackSelect(value); break; + case CMD_KNOB_VOLUME0: case CMD_KNOB_VOLUME1: case CMD_KNOB_VOLUME2: case CMD_KNOB_VOLUME3: case CMD_KNOB_VOLUME4: case CMD_KNOB_VOLUME5: case CMD_KNOB_VOLUME6: - case CMD_KNOB_VOLUME7: - case CMD_KNOB_VOLUME8: + case CMD_KNOB_VOLUME7: this->_onKnobVolumeChange(command, convertSignedMidiValue(value)); break; + case CMD_KNOB_PAN0: case CMD_KNOB_PAN1: case CMD_KNOB_PAN2: case CMD_KNOB_PAN3: case CMD_KNOB_PAN4: case CMD_KNOB_PAN5: case CMD_KNOB_PAN6: - case CMD_KNOB_PAN7: - case CMD_KNOB_PAN8: + case CMD_KNOB_PAN7: this->_onKnobPanChange(command, convertSignedMidiValue(value)); break; default: @@ -407,15 +436,10 @@ class NiMidiSurface: public BaseSurface { this->_sendSysex(CMD_TRACK_MUTED, muted ? 1 : 0, numInBank); int armed = *(int*)GetSetMediaTrackInfo(track, "I_RECARM", nullptr); this->_sendSysex(CMD_TRACK_ARMED, armed, numInBank); - - //------------------------------------------- - // ToDo: Update Volume text and Volume marker double volume = *(double*)GetSetMediaTrackInfo(track, "D_VOL", nullptr); - //char vol[2] = { volToChar_KkMk2(volume), 0 }; - //if (vol[0] != g_volBank_last[numInBank]) { - // this->_sendSysex(CMD_TRACK_VOLUME_CHANGED, 0, numInBank, vol); - // g_volBank_last[numInBank] = vol[0]; - //} + // ToDo: to save MIDI bandwidth consider checking against table with previous volumes to decide if an update is needed + this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, dbToAsciiStringDB(VAL2DB(volume))); + this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume)); //------------------------------------------- // ToDo: Update Pan text and Pan marker //------------------------------------------- @@ -476,7 +500,7 @@ class NiMidiSurface: public BaseSurface { } void _onKnobVolumeChange(unsigned char command, signed char value) { - int numInBank = command - CMD_KNOB_VOLUME1; + int numInBank = command - CMD_KNOB_VOLUME0; double dvalue = static_cast(value); MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); if (!track) { @@ -486,7 +510,7 @@ class NiMidiSurface: public BaseSurface { } void _onKnobPanChange(unsigned char command, signed char value) { - int numInBank = command - CMD_KNOB_PAN1; + int numInBank = command - CMD_KNOB_PAN0; double dvalue = static_cast(value); MediaTrack* track = CSurf_TrackFromID(numInBank + this->_bankStart, false); if (!track) { From 8c082fb2c89d28b6c87d449fe1662246bbd613fd Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Wed, 22 May 2019 18:08:01 -0700 Subject: [PATCH 31/47] Volume Text, Marker and Callback working --- src/niMidi.cpp | 56 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 435f322..9ea87a5 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -79,11 +79,11 @@ const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View // State Information -// Uses: -// - save CPU and MIDI bus resources by avoiding unnecessary updates or state information calls -// - filtering (e.g. lowpass smoothing) of values // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; +// Other potential uses: +// - save CPU and MIDI bus resources by avoiding unnecessary updates or state information calls +// - filtering (e.g. lowpass smoothing) of values // static int g_volBank_last[BANK_NUM_TRACKS] = { 0xff }; // static int g_panBank_last[BANK_NUM_TRACKS] = { 0xff }; @@ -100,7 +100,7 @@ signed char convertSignedMidiValue(unsigned char value) { char* dbToAsciiStringDB(double val) { char ascii[10] = { '0','1','2','3','4','5','6','7','8','9' }; static char s[9] = { ' ',' ', ' ', ' ' , '.' , ' ' , 'd' , 'B' , '\0' }; - int db_10 = (int)(val * 10.0); + int db_10 = (int)(val * 10.0); // one decimal point accuracy. No rounding. if (db_10 < 0) { s[0] = '-'; @@ -133,7 +133,7 @@ char* dbToAsciiStringDB(double val) { //----------------------------------------------------------------------------------------------------------------------- // The following conversion functions (C) 2006-2008 Cockos Incorporated -// ToDo: Eliminate those that will eventually not be used (e.g. volToChar) +// ToDo: Eliminate those that will eventually not be used (e.g. volToChar), also from header file static double charToVol(unsigned char val) { double pos = ((double)val*1000.0) / 127.0; @@ -173,12 +173,11 @@ static unsigned char panToChar(double pan) // End of conversion functions (C) 2006-2008 Cockos Incorporated //----------------------------------------------------------------------------------------------------------------------- +// Direct linear logarithmic conversion to KK Mk2 Meter Scaling. +// Contrary to Reaper's Peak Volume Meters the dB interval spacing on KK Mk2 displays is NOT linear. +// It is assumed that other NI Keyboards use the same scaling for the meters. +// Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB, #2 = -96dB, #1 = -infinite static unsigned char volToChar_KkMk2(double volume) { - // Direct linear logarithmic conversion to KK Mk2 Meter Scaling. - // Contrary to Reaper's Peak Volume Meters the dB interval spacing on KK Mk2 displays is NOT linear. - // It is assumed that other NI Keyboards use the same scaling for the meters. - // Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB, #2 = -96dB, #1 = -infinite - constexpr double minus48dB = 0.00398107170553497250; constexpr double minus96dB = 1.5848931924611134E-05; constexpr double m = (16.0 - 2.0) / (minus48dB - minus96dB); @@ -233,7 +232,7 @@ class NiMidiSurface: public BaseSurface { if (this->_bankStart != oldBankStart) { this->_allMixerUpdate(); } - +#ifdef DEBUG_DIAGNOSTICS // ==================================================================== // THIS TEMPORARY BLOCK IS JUST FOR TESTING // We abuse the SetSurfaceSelected callback to update the entire Mixer @@ -241,7 +240,7 @@ class NiMidiSurface: public BaseSurface { // ToDo: Remove this block as soon as the proper callbacks per parameter are in place this->_allMixerUpdate(); // ==================================================================== - +#endif // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, @@ -249,7 +248,19 @@ class NiMidiSurface: public BaseSurface { } } - protected: + virtual void SetSurfaceVolume(MediaTrack* track, double volume) override { + int id = CSurf_TrackToID(track, false); + if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { + int numInBank = id % BANK_NUM_TRACKS; + this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, dbToAsciiStringDB(VAL2DB(volume))); + this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume * 1.05925)); + } + } + + + // ToDo: Light up buttons for transport control et al (both ways, i.e. callbacks from Reaper GUI as well as when triggered from Keyboard) + +protected: void _onMidiEvent(MIDI_event_t* event) override { if (event->midi_message[0] != MIDI_CC) { return; @@ -371,9 +382,9 @@ class NiMidiSurface: public BaseSurface { // Meter information is sent to KK as array (string of chars) for all 16 channels (8 x stereo) of one bank. // A value of 0 will result in stopping to refresh meters further to right as it is interpretated as "end of string". - // The array needs one additional char at peakBank[16] set as "end of string" marker. + // peakBank[0]..peakBank[31] are used for data. The array needs one additional last char peakBank[32] set as "end of string" marker. char peakBank[(BANK_NUM_TRACKS * 2) + 1] = { 0 }; - peakBank[16] = '\0'; + peakBank[(BANK_NUM_TRACKS * 2)] = '\0'; // "end of string" marker int j = 0; double peakValue = 0; int numInBank = 0; @@ -402,7 +413,6 @@ class NiMidiSurface: public BaseSurface { peakBank[j + 1] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 } } - peakBank[j + 2] = '\0'; this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); } @@ -437,9 +447,8 @@ class NiMidiSurface: public BaseSurface { int armed = *(int*)GetSetMediaTrackInfo(track, "I_RECARM", nullptr); this->_sendSysex(CMD_TRACK_ARMED, armed, numInBank); double volume = *(double*)GetSetMediaTrackInfo(track, "D_VOL", nullptr); - // ToDo: to save MIDI bandwidth consider checking against table with previous volumes to decide if an update is needed this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, dbToAsciiStringDB(VAL2DB(volume))); - this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume)); + this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume * 1.05925)); //------------------------------------------- // ToDo: Update Pan text and Pan marker //------------------------------------------- @@ -472,7 +481,7 @@ class NiMidiSurface: public BaseSurface { // int iSel = 0; // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; ClearSelected(); - GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); // Could we instead use CSurf_OnTrackSelection? } void _onTrackNav(signed char value) { @@ -485,7 +494,7 @@ class NiMidiSurface: public BaseSurface { MediaTrack* track = CSurf_TrackFromID(id, false); int iSel = 1; // "Select" ClearSelected(); - GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); // We could also probably use CSurf_OnTrackSelection } void _onBankSelect(signed char value) { @@ -506,7 +515,9 @@ class NiMidiSurface: public BaseSurface { if (!track) { return; } - CSurf_OnVolumeChange(track, dvalue * 0.007874, true); // scaling by dividing by 127 (0.007874) + CSurf_SetSurfaceVolume(track, CSurf_OnVolumeChange(track, dvalue * 0.007874, true), nullptr); + // scaling by dividing by 127 (0.007874) + // ToDo: non linear behaviour especially in the lower volumes would be preferable. } void _onKnobPanChange(unsigned char command, signed char value) { @@ -516,7 +527,8 @@ class NiMidiSurface: public BaseSurface { if (!track) { return; } - CSurf_OnPanChange(track, dvalue * 0.00098425, true); // scaling by dividing by 127*8 (0.00098425) + CSurf_SetSurfacePan(track, CSurf_OnPanChange(track, dvalue * 0.00098425, true), nullptr); + // scaling by dividing by 127*8 (0.00098425) } void _sendCc(unsigned char command, unsigned char value) { From e814a3c12fcb36bc91c4693a0253800040f91904 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Thu, 23 May 2019 12:36:39 -0700 Subject: [PATCH 32/47] More ToDo comments --- src/niMidi.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 9ea87a5..e9c3de4 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -222,6 +222,16 @@ class NiMidiSurface: public BaseSurface { // ToDo: If the tracklist changes, we always need to call _allMixerUpdate because the change may (or may not) affect the currently visible bank + // ToDo: Consider using OnTrackSelection() rather than SetSurfaceSelected. + // Reason: SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks) + // It seems, however, that SetSurfaceSelected() may be the more robust choice because according to other people's + // findings OnTrackSelection() does not work in many situations, e.g. when track is selected via an action! The latter + // is quite common, however, and thus we may have to stick to SetSurfaceSelected(). + // See: https://github.com/reaper-oss/sws/blob/6963f7563851c8dd919db426bae825843939077f/sws_extension.cpp#L528 + // Remark: The naming of OnTrackSelection() is confusing because typically this would indicate a call from the plugin to + // Reaper to update the project (just like other calls like OnVolumeChange(), OnPlay(), .. etc) . + // When something changes in the project Reaper typically calls functions starting with Set, hence SetTrackSelected() + // seems more logical. virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { if (selected) { int id = CSurf_TrackToID(track, false); @@ -481,7 +491,7 @@ class NiMidiSurface: public BaseSurface { // int iSel = 0; // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; ClearSelected(); - GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); // Could we instead use CSurf_OnTrackSelection? + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } void _onTrackNav(signed char value) { @@ -494,7 +504,7 @@ class NiMidiSurface: public BaseSurface { MediaTrack* track = CSurf_TrackFromID(id, false); int iSel = 1; // "Select" ClearSelected(); - GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); // We could also probably use CSurf_OnTrackSelection + GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } void _onBankSelect(signed char value) { From d232fa7a2f61c1e250ff1c4d3bc4f543ebe6f245 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Thu, 23 May 2019 22:39:17 -0700 Subject: [PATCH 33/47] Changes to tracklist update the Mixer View --- src/niMidi.cpp | 44 +++++++++++++++++++++++++++++++------------- src/reaKontrol.h | 1 + 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index e9c3de4..bf84356 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -220,18 +220,31 @@ class NiMidiSurface: public BaseSurface { return "Komplete Kontrol S-series Mk2/A-series/M-series"; } - // ToDo: If the tracklist changes, we always need to call _allMixerUpdate because the change may (or may not) affect the currently visible bank - - // ToDo: Consider using OnTrackSelection() rather than SetSurfaceSelected. - // Reason: SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks) - // It seems, however, that SetSurfaceSelected() may be the more robust choice because according to other people's - // findings OnTrackSelection() does not work in many situations, e.g. when track is selected via an action! The latter - // is quite common, however, and thus we may have to stick to SetSurfaceSelected(). - // See: https://github.com/reaper-oss/sws/blob/6963f7563851c8dd919db426bae825843939077f/sws_extension.cpp#L528 - // Remark: The naming of OnTrackSelection() is confusing because typically this would indicate a call from the plugin to - // Reaper to update the project (just like other calls like OnVolumeChange(), OnPlay(), .. etc) . - // When something changes in the project Reaper typically calls functions starting with Set, hence SetTrackSelected() - // seems more logical. + // If tracklist changes update Mixer View and ensure sanity of track and bank focus + virtual void SetTrackListChange() override { + int numTracks = CSurf_NumTracks(false); + // Protect against loosing track focus that could impede track navigation. Set focus on last track in this case. + if (g_trackInFocus > numTracks) { + g_trackInFocus = numTracks; + // Unfortunately we cannot afford to explicitly select the last track automatically because this could screw up + // running actions or macros. The plugin may not manipulate track selection without the user deliberately triggering + // track selection/navigation on the keyboard (or from within Reaper). + } + // Protect against loosing bank focus. Set focus on last bank in this case. + if (this->_bankStart > numTracks) { + int lastInBank = numTracks % BANK_NUM_TRACKS; + this->_bankStart = numTracks - lastInBank; + } + // If no track is selected at all (e.g. if previously selected track got removed), then this will now also show up in the + // Mixer View. However, KK instance focus may still be present! This can be a little bit confusing for the user as typically + // the track holding the focused KK instance will also be selected. This situation gets resolved as soon as any form of + // track navigation/selection happens (from keyboard or from within Reaper). + this->_allMixerUpdate(); + } + + // Using SetSurfaceSelected() rather than OnTrackSelection(): + // SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks). + // However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15 virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { if (selected) { int id = CSurf_TrackToID(track, false); @@ -496,8 +509,13 @@ class NiMidiSurface: public BaseSurface { void _onTrackNav(signed char value) { // Move to next/previous track relative to currently focused track = last selected track + int numTracks = CSurf_NumTracks(false); + // Backstop measure to protect against unreported track removal that was not captured in SetTrackListChange callback due to race condition + if (g_trackInFocus > numTracks) { + g_trackInFocus = numTracks; + } if (((g_trackInFocus <= 1) && (value < 0)) || - ((g_trackInFocus >= CSurf_NumTracks(false))) && (value >0 )) { + ((g_trackInFocus >= numTracks) && (value >0 ))) { return; } int id = g_trackInFocus + value; diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 90f6ece..1129136 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -35,6 +35,7 @@ #define REAPERAPI_WANT_Main_OnCommand #define REAPERAPI_WANT_CSurf_ScrubAmt #define REAPERAPI_WANT_GetSetMediaTrackInfo +#define REAPERAPI_WANT_CSurf_SetTrackListChange #define REAPERAPI_WANT_CSurf_SetSurfaceVolume #define REAPERAPI_WANT_CSurf_SetSurfacePan #define REAPERAPI_WANT_CSurf_OnVolumeChange From 291bba9dde2b1628bd6fa70eef34d0db6c1810a8 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Fri, 24 May 2019 12:05:53 -0700 Subject: [PATCH 34/47] Transport Button Lights working includes Repeat (=Loop) button --- src/niMidi.cpp | 37 ++++++++++++++++++++++++++++++++++++- src/reaKontrol.h | 2 ++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index bf84356..151985a 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -220,6 +220,41 @@ class NiMidiSurface: public BaseSurface { return "Komplete Kontrol S-series Mk2/A-series/M-series"; } + // Update transport button lights + virtual void SetPlayState(bool play, bool pause, bool rec) override { + if (rec) { + this->_sendCc(CMD_REC, 1); + } + else { + this->_sendCc(CMD_REC, 0); + } + if (pause) { + this->_sendCc(CMD_PLAY, 1); + this->_sendCc(CMD_STOP, 1); // since there is no Pause button on KK we indicate it with both Play and Stop lit + } + else if (play) { + this->_sendCc(CMD_PLAY, 1); + this->_sendCc(CMD_STOP, 0); + } + else { + this->_sendCc(CMD_PLAY, 0); + this->_sendCc(CMD_STOP, 1); + } + } + + // Update repeat (aka loop) button light + virtual void SetRepeatState(bool rep) override { + if (rep) { + this->_sendCc(CMD_LOOP, 1); + } + else { + this->_sendCc(CMD_LOOP, 0); + } + } + + // ToDo: add more button lights: METRO, AUTO, tbd: undo/redo, clear, quantize, tempo + // ToDo: mute and solo buttons for the selected track should be handled in their dedicated calls + // If tracklist changes update Mixer View and ensure sanity of track and bank focus virtual void SetTrackListChange() override { int numTracks = CSurf_NumTracks(false); @@ -281,7 +316,7 @@ class NiMidiSurface: public BaseSurface { } - // ToDo: Light up buttons for transport control et al (both ways, i.e. callbacks from Reaper GUI as well as when triggered from Keyboard) + protected: void _onMidiEvent(MIDI_event_t* event) override { diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 1129136..639f4fe 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -38,6 +38,8 @@ #define REAPERAPI_WANT_CSurf_SetTrackListChange #define REAPERAPI_WANT_CSurf_SetSurfaceVolume #define REAPERAPI_WANT_CSurf_SetSurfacePan +#define REAPERAPI_WANT_CSurf_SetPlayState +#define REAPERAPI_WANT_CSurf_SetRepeatState #define REAPERAPI_WANT_CSurf_OnVolumeChange #define REAPERAPI_WANT_CSurf_OnPanChange #define REAPERAPI_WANT_GetPlayState From 9657b306e1a65a3f0f23ba32d9f1b1b3c0609db3 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Fri, 24 May 2019 22:31:34 -0700 Subject: [PATCH 35/47] Pan Marker and Callback working - also simplified code for Volume text by using Reaper's built in conversion --- src/niMidi.cpp | 104 +++++++++++------------------------------------ src/reaKontrol.h | 5 ++- 2 files changed, 27 insertions(+), 82 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 151985a..72eba36 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -96,71 +96,6 @@ signed char convertSignedMidiValue(unsigned char value) { return value - 128; } -// Convert dB value to decimal ASCII string -char* dbToAsciiStringDB(double val) { - char ascii[10] = { '0','1','2','3','4','5','6','7','8','9' }; - static char s[9] = { ' ',' ', ' ', ' ' , '.' , ' ' , 'd' , 'B' , '\0' }; - int db_10 = (int)(val * 10.0); // one decimal point accuracy. No rounding. - - if (db_10 < 0) { - s[0] = '-'; - db_10 = -(db_10); - } - else { s[0] = ' '; } - - int c1000 = db_10 / 1000; int m1000 = c1000 * 1000; - int c100 = (db_10 - m1000) / 100; int m100 = c100 * 100; - int c10 = (db_10 - m1000 - m100) / 10; int m10 = c10 * 10; - int c1 = db_10 - m1000 - m100 - m10; - - if (c1000) { - s[1] = ascii[c1000]; - s[2] = ascii[c100]; - } - else { - s[1] = ' '; - if (c100) { - s[2] = ascii[c100]; - } - else { - s[2] = ' '; - } - } - s[3] = ascii[c10]; - s[5] = ascii[c1]; - return s; -} - -//----------------------------------------------------------------------------------------------------------------------- -// The following conversion functions (C) 2006-2008 Cockos Incorporated -// ToDo: Eliminate those that will eventually not be used (e.g. volToChar), also from header file -static double charToVol(unsigned char val) -{ - double pos = ((double)val*1000.0) / 127.0; - pos = SLIDER2DB(pos); - return DB2VAL(pos); - -} - -static unsigned char volToChar(double vol) -{ - double d = (DB2SLIDER(VAL2DB(vol))*127.0 / 1000.0); - if (d < 0.0)d = 0.0; - else if (d > 127.0)d = 127.0; - - return (unsigned char)(d + 0.5); -} - -static double charToPan(unsigned char val) -{ - double pos = ((double)val*1000.0 + 0.5) / 127.0; - - pos = (pos - 500.0) / 500.0; - if (fabs(pos) < 0.08) pos = 0.0; - - return pos; -} - static unsigned char panToChar(double pan) { pan = (pan + 1.0)*63.5; @@ -170,8 +105,6 @@ static unsigned char panToChar(double pan) return (unsigned char)(pan + 0.5); } -// End of conversion functions (C) 2006-2008 Cockos Incorporated -//----------------------------------------------------------------------------------------------------------------------- // Direct linear logarithmic conversion to KK Mk2 Meter Scaling. // Contrary to Reaper's Peak Volume Meters the dB interval spacing on KK Mk2 displays is NOT linear. @@ -310,15 +243,25 @@ class NiMidiSurface: public BaseSurface { int id = CSurf_TrackToID(track, false); if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { int numInBank = id % BANK_NUM_TRACKS; - this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, dbToAsciiStringDB(VAL2DB(volume))); + char volText[64] = { 0 }; + mkvolstr(volText, volume); + this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, volText); this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume * 1.05925)); } } + virtual void SetSurfacePan(MediaTrack* track, double pan) override { + int id = CSurf_TrackToID(track, false); + if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { + int numInBank = id % BANK_NUM_TRACKS; + char panText[64]; + mkpanstr(panText, pan); + this->_sendSysex(CMD_TRACK_PAN_TEXT, 0, numInBank, panText); // KK firmware 0.5.7 uses internal text + this->_sendCc((CMD_KNOB_PAN0 + numInBank), panToChar(pan)); + } + } - - -protected: + protected: void _onMidiEvent(MIDI_event_t* event) override { if (event->midi_message[0] != MIDI_CC) { return; @@ -505,20 +448,21 @@ class NiMidiSurface: public BaseSurface { int armed = *(int*)GetSetMediaTrackInfo(track, "I_RECARM", nullptr); this->_sendSysex(CMD_TRACK_ARMED, armed, numInBank); double volume = *(double*)GetSetMediaTrackInfo(track, "D_VOL", nullptr); - this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, dbToAsciiStringDB(VAL2DB(volume))); - this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume * 1.05925)); - //------------------------------------------- - // ToDo: Update Pan text and Pan marker - //------------------------------------------- - + char volText[64]; + mkvolstr(volText, volume); + this->_sendSysex(CMD_TRACK_VOLUME_TEXT, 0, numInBank, volText); + this->_sendCc((CMD_KNOB_VOLUME0 + numInBank), volToChar_KkMk2(volume * 1.05925)); + double pan = *(double*)GetSetMediaTrackInfo(track, "D_PAN", nullptr); + char panText[64]; + mkpanstr(panText, pan); + this->_sendSysex(CMD_TRACK_PAN_TEXT, 0, numInBank, panText); // KK firmware 0.5.7 uses internal text + this->_sendCc((CMD_KNOB_PAN0 + numInBank), panToChar(pan)); char* name = (char*)GetSetMediaTrackInfo(track, "P_NAME", nullptr); if (!name) { name = ""; } this->_sendSysex(CMD_TRACK_NAME, 0, numInBank, name); - // todo: level meters, volume, pan - } - // todo: navigate tracks, navigate banks. NOTE: probably not here + } } void ClearSelected() { diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 639f4fe..8e1d52e 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -44,8 +44,9 @@ #define REAPERAPI_WANT_CSurf_OnPanChange #define REAPERAPI_WANT_GetPlayState #define REAPERAPI_WANT_Track_GetPeakInfo -#define REAPERAPI_WANT_DB2SLIDER -#define REAPERAPI_WANT_SLIDER2DB +#define REAPERAPI_WANT_mkvolstr +#define REAPERAPI_WANT_mkpanstr + #include #include #include From 4c93513c98b138e97be60a9d26f8a9f2f1d4e52b Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sat, 25 May 2019 00:11:41 -0700 Subject: [PATCH 36/47] Volume & Pan change of selected track via 4D encoder - also changed some constant names to reflect their actual meaning --- src/niMidi.cpp | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 72eba36..a70a8de 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -45,7 +45,7 @@ const unsigned char CMD_NAV_SCENES = 0x33; const unsigned char CMD_MOVE_TRANSPORT = 0x34; const unsigned char CMD_MOVE_LOOP = 0x35; const unsigned char CMD_TRACK_AVAIL = 0x40; -const unsigned char CMD_SEL_TRACK_PARAMS_CHANGED = 0x41; +const unsigned char CMD_SET_KK_INSTANCE = 0x41; const unsigned char CMD_TRACK_SELECTED = 0x42; const unsigned char CMD_TRACK_MUTED = 0x43; const unsigned char CMD_TRACK_SOLOED = 0x44; @@ -70,8 +70,8 @@ const unsigned char CMD_KNOB_PAN4 = 0x5c; const unsigned char CMD_KNOB_PAN5 = 0x5d; const unsigned char CMD_KNOB_PAN6 = 0x5e; const unsigned char CMD_KNOB_PAN7 = 0x5f; -const unsigned char CMD_CHANGE_VOLUME = 0x64; -const unsigned char CMD_CHANGE_PAN = 0x65; +const unsigned char CMD_CHANGE_SEL_TRACK_VOLUME = 0x64; +const unsigned char CMD_CHANGE_SEL_TRACK_PAN = 0x65; const unsigned char CMD_TOGGLE_MUTE = 0x66; const unsigned char CMD_TOGGLE_SOLO = 0x67; @@ -81,11 +81,6 @@ const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track // State Information // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; -// Other potential uses: -// - save CPU and MIDI bus resources by avoiding unnecessary updates or state information calls -// - filtering (e.g. lowpass smoothing) of values -// static int g_volBank_last[BANK_NUM_TRACKS] = { 0xff }; -// static int g_panBank_last[BANK_NUM_TRACKS] = { 0xff }; // Convert a signed 7 bit MIDI value to a signed char. // That is, convertSignedMidiValue(127) will return -1. @@ -215,8 +210,7 @@ class NiMidiSurface: public BaseSurface { // However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15 virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { if (selected) { - int id = CSurf_TrackToID(track, false); - g_trackInFocus = id; + int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; int oldBankStart = this->_bankStart; this->_bankStart = id - numInBank; @@ -232,9 +226,11 @@ class NiMidiSurface: public BaseSurface { this->_allMixerUpdate(); // ==================================================================== #endif - // Let Keyboard know about changed track selection + // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); - this->_sendSysex(CMD_SEL_TRACK_PARAMS_CHANGED, 0, 0, + // Set KK Instance Focus + g_trackInFocus = id; + this->_sendSysex(CMD_SET_KK_INSTANCE, 0, 0, getKkInstanceName(track)); } } @@ -361,6 +357,12 @@ class NiMidiSurface: public BaseSurface { case CMD_KNOB_PAN7: this->_onKnobPanChange(command, convertSignedMidiValue(value)); break; + case CMD_CHANGE_SEL_TRACK_VOLUME: + this->_onSelTrackVolumeChange(convertSignedMidiValue(value)); + break; + case CMD_CHANGE_SEL_TRACK_PAN: + this->_onSelTrackPanChange(convertSignedMidiValue(value)); + break; default: #ifdef BASIC_DIAGNOSTICS ostringstream s; @@ -538,6 +540,26 @@ class NiMidiSurface: public BaseSurface { // scaling by dividing by 127*8 (0.00098425) } + void _onSelTrackVolumeChange(signed char value) { + double dvalue = static_cast(value); + MediaTrack* track = CSurf_TrackFromID(g_trackInFocus, false); + if (!track) { + return; + } + CSurf_SetSurfaceVolume(track, CSurf_OnVolumeChange(track, dvalue * 0.007874, true), nullptr); + // ToDo: Consider different scaling than with KnobVolumeChange + // ToDo: non linear behaviour especially in the lower volumes would be preferable. + } + + void _onSelTrackPanChange(signed char value) { + double dvalue = static_cast(value); + MediaTrack* track = CSurf_TrackFromID(g_trackInFocus, false); + if (!track) { + return; + } + CSurf_SetSurfacePan(track, CSurf_OnPanChange(track, dvalue * 0.00098425, true), nullptr); + } + void _sendCc(unsigned char command, unsigned char value) { if (this->_midiOut) { this->_midiOut->Send(MIDI_CC, command, value, -1); From 72f06ea2faea3ed5c58b675e8f663f243d283ffb Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sun, 26 May 2019 17:10:16 -0700 Subject: [PATCH 37/47] Mute Solo Recarm - still missing: Mute Solo selected track incl. light indicators --- src/niMidi.cpp | 106 ++++++++++++++++++++++++++++++++++++++++------- src/reaKontrol.h | 6 +++ 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index a70a8de..4c588a5 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -72,8 +72,8 @@ const unsigned char CMD_KNOB_PAN6 = 0x5e; const unsigned char CMD_KNOB_PAN7 = 0x5f; const unsigned char CMD_CHANGE_SEL_TRACK_VOLUME = 0x64; const unsigned char CMD_CHANGE_SEL_TRACK_PAN = 0x65; -const unsigned char CMD_TOGGLE_MUTE = 0x66; -const unsigned char CMD_TOGGLE_SOLO = 0x67; +const unsigned char CMD_TOGGLE_SEL_TRACK_MUTE = 0x66; +const unsigned char CMD_TOGGLE_SEL_TRACK_SOLO = 0x67; const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View @@ -129,6 +129,10 @@ static unsigned char volToChar_KkMk2(double volume) { } +// ToDo: Some Reaper callbacks (Set....) are called unecessarily (?) often in Reaper, see various forum reports & comments from schwa +// => Consider state checking statements at beginning of every callback to return immediately if call is not necessary -> save CPU + + class NiMidiSurface: public BaseSurface { public: NiMidiSurface(int inDev, int outDev) @@ -181,7 +185,7 @@ class NiMidiSurface: public BaseSurface { } // ToDo: add more button lights: METRO, AUTO, tbd: undo/redo, clear, quantize, tempo - // ToDo: mute and solo buttons for the selected track should be handled in their dedicated calls + // ToDo: bank buttons // If tracklist changes update Mixer View and ensure sanity of track and bank focus virtual void SetTrackListChange() override { @@ -208,6 +212,7 @@ class NiMidiSurface: public BaseSurface { // Using SetSurfaceSelected() rather than OnTrackSelection(): // SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks). // However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15 + // See also notes in SetSurfaceRecArm virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { if (selected) { int id = CSurf_TrackToID(track, false); @@ -217,15 +222,6 @@ class NiMidiSurface: public BaseSurface { if (this->_bankStart != oldBankStart) { this->_allMixerUpdate(); } -#ifdef DEBUG_DIAGNOSTICS - // ==================================================================== - // THIS TEMPORARY BLOCK IS JUST FOR TESTING - // We abuse the SetSurfaceSelected callback to update the entire Mixer - // on every track selection change. - // ToDo: Remove this block as soon as the proper callbacks per parameter are in place - this->_allMixerUpdate(); - // ==================================================================== -#endif // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); // Set KK Instance Focus @@ -256,6 +252,46 @@ class NiMidiSurface: public BaseSurface { this->_sendCc((CMD_KNOB_PAN0 + numInBank), panToChar(pan)); } } + + virtual void SetSurfaceMute(MediaTrack* track, bool mute) override { + int id = CSurf_TrackToID(track, false); + if (id == g_trackInFocus) { + this->_sendCc(CMD_TOGGLE_SEL_TRACK_MUTE, mute ? 1 : 0); // ToDo: does not work yet - why? Is an extra track_available required? Or instance? Or SysEx? + } + if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { + int numInBank = id % BANK_NUM_TRACKS; + this->_sendSysex(CMD_TRACK_MUTED, mute ? 1 : 0, numInBank); + } + } + + // Note: Solo in Reaper can have different meanings (Solo In Place, Solo In Front and much more -> Reaper Preferences) + virtual void SetSurfaceSolo(MediaTrack* track, bool solo) override { + int id = CSurf_TrackToID(track, false); + // Reaper API has a "special" behavior: https://forum.cockos.com/showthread.php?p=2139125#post2139125 + // Ignore solo on master + if (id == 0) { + return; + } + if (id == g_trackInFocus) { + this->_sendCc(CMD_TOGGLE_SEL_TRACK_SOLO, solo ? 1 : 0); // ToDo: does not work yet - why? Is an extra track_available required? Or instance? Or SysEx? + } + if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { + int numInBank = id % BANK_NUM_TRACKS; + this->_sendSysex(CMD_TRACK_SOLOED, solo ? 1 : 0, numInBank); + } + } + + virtual void SetSurfaceRecArm(MediaTrack* track, bool armed) override { + // ToDo: ISSUE: it seems (confirmed by forum reports) that record arm in Reaper leads to a cascade of callbacks + // In our case it seems that also the SetSurfaceSelected gets triggered although no track selection change happened + // This may even depend on settings in preferences? + // => Consider filtering this somehow with state variables in SetSurfaceSelected or just live with it... + int id = CSurf_TrackToID(track, false); + if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { + int numInBank = id % BANK_NUM_TRACKS; + this->_sendSysex(CMD_TRACK_ARMED, armed ? 1 : 0, numInBank); + } + } protected: void _onMidiEvent(MIDI_event_t* event) override { @@ -337,6 +373,14 @@ class NiMidiSurface: public BaseSurface { // Select a track from current bank in Mixer Mode with top row buttons this->_onTrackSelect(value); break; + case CMD_TRACK_MUTED: + // Toggle mute for a a track from current bank in Mixer Mode with top row buttons + this->_onTrackMute(value); + break; + case CMD_TRACK_SOLOED: + // Toggle solo for a a track from current bank in Mixer Mode with top row buttons + this->_onTrackSolo(value); + break; case CMD_KNOB_VOLUME0: case CMD_KNOB_VOLUME1: case CMD_KNOB_VOLUME2: @@ -379,6 +423,10 @@ class NiMidiSurface: public BaseSurface { void _peakMixerUpdate() override { // Peak meters. Note: Reaper reports peak, NOT VU + // ToDo: Find a way to deal with Reaper's buggy (?) Track_GetPeakInfo() call. + // - reports peak values when track is muted + // - does not report peak values when track is muted AND soloed + // Should we keep track in plugin of mute an solo states (of tracks in bank)? Maybe the best way to deal with it... // ToDo: Peak Hold in KK display shall be erased immediately when changing bank // ToDo: Peak Hold in KK display shall be erased after decay time t when track muted or no signal. // ToDo: Explore the effect of sending CMD_SEL_TRACK_PARAMS_CHANGED after sending CMD_TRACK_VU @@ -404,10 +452,13 @@ class NiMidiSurface: public BaseSurface { break; } j = 2 * numInBank; - // Muted tracks still report peak levels => ignore these + // Muted tracks still report peak levels => ignore these if the track is not soloed + // ToDo: Maybe best to keep track of mute and solo states (of tracks in bank) locally in plugin... if (*(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr)) { - peakBank[j] = 1; - peakBank[j+1] = 1; + if (*(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr) == 0) { + peakBank[j] = 1; + peakBank[j + 1] = 1; + } } else { peakValue = Track_GetPeakInfo(track, 0); // left channel @@ -440,6 +491,7 @@ class NiMidiSurface: public BaseSurface { if (!track) { break; } + // ToDo: Consider indicating master track this->_sendSysex(CMD_TRACK_AVAIL, TRTYPE_UNSPEC, numInBank); int selected = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr); this->_sendSysex(CMD_TRACK_SELECTED, selected, numInBank); @@ -517,6 +569,26 @@ class NiMidiSurface: public BaseSurface { this->_allMixerUpdate(); } + void _onTrackMute(unsigned char numInBank) { + int id = this->_bankStart + numInBank; + if ((id == 0) || (id > CSurf_NumTracks(false))) { + return; + } + MediaTrack* track = CSurf_TrackFromID(id, false); + bool muted = *(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr); + CSurf_OnMuteChange(track, muted ? 0 : 1); + } + + void _onTrackSolo(unsigned char numInBank) { + int id = this->_bankStart + numInBank; + if ((id == 0) || (id > CSurf_NumTracks(false))) { + return; + } + MediaTrack* track = CSurf_TrackFromID(id, false); + int soloState = *(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr); + CSurf_OnSoloChange(track, soloState == 0 ? 1 : 0); // ToDo: Consider settings solo state to 2 (soloed in place) + } + void _onKnobVolumeChange(unsigned char command, signed char value) { int numInBank = command - CMD_KNOB_VOLUME0; double dvalue = static_cast(value); @@ -560,6 +632,10 @@ class NiMidiSurface: public BaseSurface { CSurf_SetSurfacePan(track, CSurf_OnPanChange(track, dvalue * 0.00098425, true), nullptr); } + // ToDo: toggle mute and solo for the selected track + + // ToDo: toggle automation mode + void _sendCc(unsigned char command, unsigned char value) { if (this->_midiOut) { this->_midiOut->Send(MIDI_CC, command, value, -1); diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 8e1d52e..0fe11b8 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -10,6 +10,7 @@ #include +// ToDo: Cleanup REAPERAPI WANT list #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_GetNumMIDIInputs #define REAPERAPI_WANT_GetNumMIDIInputs @@ -40,8 +41,13 @@ #define REAPERAPI_WANT_CSurf_SetSurfacePan #define REAPERAPI_WANT_CSurf_SetPlayState #define REAPERAPI_WANT_CSurf_SetRepeatState +#define REAPERAPI_WANT_CSurf_SetSurfaceMute +#define REAPERAPI_WANT_CSurf_SetSurfaceSolo +#define REAPERAPI_WANT_CSurf_SetSurfaceRecArm #define REAPERAPI_WANT_CSurf_OnVolumeChange #define REAPERAPI_WANT_CSurf_OnPanChange +#define REAPERAPI_WANT_CSurf_OnMuteChange +#define REAPERAPI_WANT_CSurf_OnSoloChange #define REAPERAPI_WANT_GetPlayState #define REAPERAPI_WANT_Track_GetPeakInfo #define REAPERAPI_WANT_mkvolstr From 48d9cc28595b0516b5be03ebdd49edbbadce5e31 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sun, 26 May 2019 23:29:24 -0700 Subject: [PATCH 38/47] Mute and Solo Working - Commands and callbacks working - Rec Arm callback working - Small fix for meters (muted & soloed is now considered) --- src/niMidi.cpp | 134 ++++++++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 51 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 4c588a5..f6e7bc6 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -28,22 +28,22 @@ const unsigned char CMD_GOODBYE = 0x02; const unsigned char CMD_PLAY = 0x10; const unsigned char CMD_RESTART = 0x11; const unsigned char CMD_REC = 0x12; -const unsigned char CMD_COUNT = 0x13; +const unsigned char CMD_COUNT = 0x13; // ToDo: ? const unsigned char CMD_STOP = 0x14; -const unsigned char CMD_CLEAR = 0x15; +const unsigned char CMD_CLEAR = 0x15; // ToDo: ? const unsigned char CMD_LOOP = 0x16; const unsigned char CMD_METRO = 0x17; const unsigned char CMD_TEMPO = 0x18; const unsigned char CMD_UNDO = 0x20; const unsigned char CMD_REDO = 0x21; -const unsigned char CMD_QUANTIZE = 0x22; -const unsigned char CMD_AUTO = 0x23; +const unsigned char CMD_QUANTIZE = 0x22; // ToDo: ? +const unsigned char CMD_AUTO = 0x23; // ToDo: Toggle between automation modes const unsigned char CMD_NAV_TRACKS = 0x30; const unsigned char CMD_NAV_BANKS = 0x31; const unsigned char CMD_NAV_CLIPS = 0x32; -const unsigned char CMD_NAV_SCENES = 0x33; +const unsigned char CMD_NAV_SCENES = 0x33; // not used const unsigned char CMD_MOVE_TRANSPORT = 0x34; -const unsigned char CMD_MOVE_LOOP = 0x35; +const unsigned char CMD_MOVE_LOOP = 0x35; // not used const unsigned char CMD_TRACK_AVAIL = 0x40; const unsigned char CMD_SET_KK_INSTANCE = 0x41; const unsigned char CMD_TRACK_SELECTED = 0x42; @@ -54,6 +54,7 @@ const unsigned char CMD_TRACK_VOLUME_TEXT = 0x46; const unsigned char CMD_TRACK_PAN_TEXT = 0x47; const unsigned char CMD_TRACK_NAME = 0x48; const unsigned char CMD_TRACK_VU = 0x49; +const unsigned char CMD_TRACK_MUTED_BY_SOLO = 0x4A; // ToDo: Implement this in _allMixerUpdate AND _SetSurfaceSolo (or less efficiently _peakMixerUpdate) const unsigned char CMD_KNOB_VOLUME0 = 0x50; const unsigned char CMD_KNOB_VOLUME1 = 0x51; const unsigned char CMD_KNOB_VOLUME2 = 0x52; @@ -70,10 +71,16 @@ const unsigned char CMD_KNOB_PAN4 = 0x5c; const unsigned char CMD_KNOB_PAN5 = 0x5d; const unsigned char CMD_KNOB_PAN6 = 0x5e; const unsigned char CMD_KNOB_PAN7 = 0x5f; +const unsigned char CMD_PLAY_CLIP = 0x60; // not used +const unsigned char CMD_STOP_CLIP = 0x61; // not used +const unsigned char CMD_PLAY_SCENE = 0x62; // not used +const unsigned char CMD_RECORD_SESSION = 0x63; // not used const unsigned char CMD_CHANGE_SEL_TRACK_VOLUME = 0x64; const unsigned char CMD_CHANGE_SEL_TRACK_PAN = 0x65; const unsigned char CMD_TOGGLE_SEL_TRACK_MUTE = 0x66; const unsigned char CMD_TOGGLE_SEL_TRACK_SOLO = 0x67; +const unsigned char CMD_SEL_TRACK_AVAILABLE = 0x68; // ToDo: ? +const unsigned char CMD_SEL_TRACK_MUTED_BY_SOLO = 0x69; // ToDo: ? const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View @@ -82,9 +89,9 @@ const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; -// Convert a signed 7 bit MIDI value to a signed char. -// That is, convertSignedMidiValue(127) will return -1. signed char convertSignedMidiValue(unsigned char value) { + // Convert a signed 7 bit MIDI value to a signed char. + // That is, convertSignedMidiValue(127) will return -1. if (value <= 63) { return value; } @@ -101,11 +108,11 @@ static unsigned char panToChar(double pan) return (unsigned char)(pan + 0.5); } -// Direct linear logarithmic conversion to KK Mk2 Meter Scaling. -// Contrary to Reaper's Peak Volume Meters the dB interval spacing on KK Mk2 displays is NOT linear. -// It is assumed that other NI Keyboards use the same scaling for the meters. -// Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB, #2 = -96dB, #1 = -infinite static unsigned char volToChar_KkMk2(double volume) { + // Direct linear logarithmic conversion to KK Mk2 Meter Scaling. + // Contrary to Reaper's Peak Volume Meters the dB interval spacing on KK Mk2 displays is NOT linear. + // It is assumed that other NI Keyboards use the same scaling for the meters. + // Midi #127 = +6dB #106 = 0dB, #68 = -12dB, #38 = -24dB, #16 = -48dB, #2 = -96dB, #1 = -infinite constexpr double minus48dB = 0.00398107170553497250; constexpr double minus96dB = 1.5848931924611134E-05; constexpr double m = (16.0 - 2.0) / (minus48dB - minus96dB); @@ -124,15 +131,15 @@ static unsigned char volToChar_KkMk2(double volume) { else { result = 0.5; // will be rounded to 1 } - if (result > 126.5) result = 126.5; + if (result > 126.5) { + result = 126.5; + } return (unsigned char)(result + 0.5); // rounding and make sure that minimum value returned is 1 } - // ToDo: Some Reaper callbacks (Set....) are called unecessarily (?) often in Reaper, see various forum reports & comments from schwa // => Consider state checking statements at beginning of every callback to return immediately if call is not necessary -> save CPU - class NiMidiSurface: public BaseSurface { public: NiMidiSurface(int inDev, int outDev) @@ -151,9 +158,9 @@ class NiMidiSurface: public BaseSurface { virtual const char* GetDescString() override { return "Komplete Kontrol S-series Mk2/A-series/M-series"; } - - // Update transport button lights + virtual void SetPlayState(bool play, bool pause, bool rec) override { + // Update transport button lights if (rec) { this->_sendCc(CMD_REC, 1); } @@ -173,9 +180,9 @@ class NiMidiSurface: public BaseSurface { this->_sendCc(CMD_STOP, 1); } } - - // Update repeat (aka loop) button light + virtual void SetRepeatState(bool rep) override { + // Update repeat (aka loop) button light if (rep) { this->_sendCc(CMD_LOOP, 1); } @@ -187,8 +194,11 @@ class NiMidiSurface: public BaseSurface { // ToDo: add more button lights: METRO, AUTO, tbd: undo/redo, clear, quantize, tempo // ToDo: bank buttons - // If tracklist changes update Mixer View and ensure sanity of track and bank focus + // ToDo: If track name changes the mixer view shall be updated "immediately". This is currently not captured via SetTrackList change! + // We may have to poll for such a change - best candidate would be SetSurfaceSelected..... + virtual void SetTrackListChange() override { + // If tracklist changes update Mixer View and ensure sanity of track and bank focus int numTracks = CSurf_NumTracks(false); // Protect against loosing track focus that could impede track navigation. Set focus on last track in this case. if (g_trackInFocus > numTracks) { @@ -199,8 +209,8 @@ class NiMidiSurface: public BaseSurface { } // Protect against loosing bank focus. Set focus on last bank in this case. if (this->_bankStart > numTracks) { - int lastInBank = numTracks % BANK_NUM_TRACKS; - this->_bankStart = numTracks - lastInBank; + int lastInLastBank = numTracks % BANK_NUM_TRACKS; + this->_bankStart = numTracks - lastInLastBank; } // If no track is selected at all (e.g. if previously selected track got removed), then this will now also show up in the // Mixer View. However, KK instance focus may still be present! This can be a little bit confusing for the user as typically @@ -208,12 +218,12 @@ class NiMidiSurface: public BaseSurface { // track navigation/selection happens (from keyboard or from within Reaper). this->_allMixerUpdate(); } - - // Using SetSurfaceSelected() rather than OnTrackSelection(): - // SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks). - // However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15 - // See also notes in SetSurfaceRecArm + virtual void SetSurfaceSelected(MediaTrack* track, bool selected) override { + // Using SetSurfaceSelected() rather than OnTrackSelection(): + // SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks). + // However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15 + // See also notes in SetSurfaceRecArm if (selected) { int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; @@ -263,12 +273,13 @@ class NiMidiSurface: public BaseSurface { this->_sendSysex(CMD_TRACK_MUTED, mute ? 1 : 0, numInBank); } } - - // Note: Solo in Reaper can have different meanings (Solo In Place, Solo In Front and much more -> Reaper Preferences) + virtual void SetSurfaceSolo(MediaTrack* track, bool solo) override { + // Note: Solo in Reaper can have different meanings (Solo In Place, Solo In Front and much more -> Reaper Preferences) int id = CSurf_TrackToID(track, false); // Reaper API has a "special" behavior: https://forum.cockos.com/showthread.php?p=2139125#post2139125 // Ignore solo on master + // ToDo: Consider implementing MUTED_BY_SOLO here together with a complete solo status update for the bank! if (id == 0) { return; } @@ -407,6 +418,12 @@ class NiMidiSurface: public BaseSurface { case CMD_CHANGE_SEL_TRACK_PAN: this->_onSelTrackPanChange(convertSignedMidiValue(value)); break; + case CMD_TOGGLE_SEL_TRACK_MUTE: + this->_onSelTrackMute(); + break; + case CMD_TOGGLE_SEL_TRACK_SOLO: + this->_onSelTrackSolo(); + break; default: #ifdef BASIC_DIAGNOSTICS ostringstream s; @@ -423,10 +440,6 @@ class NiMidiSurface: public BaseSurface { void _peakMixerUpdate() override { // Peak meters. Note: Reaper reports peak, NOT VU - // ToDo: Find a way to deal with Reaper's buggy (?) Track_GetPeakInfo() call. - // - reports peak values when track is muted - // - does not report peak values when track is muted AND soloed - // Should we keep track in plugin of mute an solo states (of tracks in bank)? Maybe the best way to deal with it... // ToDo: Peak Hold in KK display shall be erased immediately when changing bank // ToDo: Peak Hold in KK display shall be erased after decay time t when track muted or no signal. // ToDo: Explore the effect of sending CMD_SEL_TRACK_PARAMS_CHANGED after sending CMD_TRACK_VU @@ -434,17 +447,17 @@ class NiMidiSurface: public BaseSurface { // Meter information is sent to KK as array (string of chars) for all 16 channels (8 x stereo) of one bank. // A value of 0 will result in stopping to refresh meters further to right as it is interpretated as "end of string". // peakBank[0]..peakBank[31] are used for data. The array needs one additional last char peakBank[32] set as "end of string" marker. - char peakBank[(BANK_NUM_TRACKS * 2) + 1] = { 0 }; + char peakBank[(BANK_NUM_TRACKS * 2) + 1]; peakBank[(BANK_NUM_TRACKS * 2)] = '\0'; // "end of string" marker int j = 0; double peakValue = 0; int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; - int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) + int numTracks = CSurf_NumTracks(false); if (bankEnd > numTracks) { bankEnd = numTracks; - int lastInBank = numTracks % BANK_NUM_TRACKS; - peakBank[(lastInBank +1) * 2] = '\0'; // end of string (no tracks available further to the right) + int lastInLastBank = numTracks % BANK_NUM_TRACKS; + peakBank[(lastInLastBank + 1) * 2] = '\0'; // end of string (no tracks available further to the right) } for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { MediaTrack* track = CSurf_TrackFromID(id, false); @@ -453,12 +466,11 @@ class NiMidiSurface: public BaseSurface { } j = 2 * numInBank; // Muted tracks still report peak levels => ignore these if the track is not soloed - // ToDo: Maybe best to keep track of mute and solo states (of tracks in bank) locally in plugin... - if (*(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr)) { - if (*(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr) == 0) { - peakBank[j] = 1; - peakBank[j + 1] = 1; - } + // ToDo: Consider to keep track of mute and solo states (of tracks in bank) locally in plugin - saves CPU + if ((*(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr)) && + (*(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr) == 0)) { + peakBank[j] = 1; + peakBank[j + 1] = 1; } else { peakValue = Track_GetPeakInfo(track, 0); // left channel @@ -477,12 +489,12 @@ class NiMidiSurface: public BaseSurface { void _allMixerUpdate() { int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; // avoid ambiguity: track counting always zero based - int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) + int numTracks = CSurf_NumTracks(false); if (bankEnd > numTracks) { bankEnd = numTracks; // Mark additional bank tracks as not available - int lastInBank = numTracks % BANK_NUM_TRACKS; - for (int i = 7; i > lastInBank; --i) { + int lastInLastBank = numTracks % BANK_NUM_TRACKS; + for (int i = 7; i > lastInLastBank; --i) { this->_sendSysex(CMD_TRACK_AVAIL, 0, i); } } @@ -519,7 +531,7 @@ class NiMidiSurface: public BaseSurface { } } - void ClearSelected() { + void _clearAllSelectedTracks() { // Clear all selected tracks. Copyright (c) 2010 and later Tim Payne (SWS) int iSel = 0; for (int i = 0; i <= GetNumTracks(); i++) // really ALL tracks, hence no use of CSurf_NumTracks @@ -536,7 +548,7 @@ class NiMidiSurface: public BaseSurface { // If we rather wanted to "Toggle" than just "Select" we would use: // int iSel = 0; // int iSel = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr) ? 0 : 1; - ClearSelected(); + _clearAllSelectedTracks(); GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } @@ -554,14 +566,14 @@ class NiMidiSurface: public BaseSurface { int id = g_trackInFocus + value; MediaTrack* track = CSurf_TrackFromID(id, false); int iSel = 1; // "Select" - ClearSelected(); + _clearAllSelectedTracks(); GetSetMediaTrackInfo(track, "I_SELECTED", &iSel); } void _onBankSelect(signed char value) { // Manually switch the bank visible in Mixer View WITHOUT influencing track selection int newBankStart = this->_bankStart + (value * BANK_NUM_TRACKS); - int numTracks = CSurf_NumTracks(false); // If we ever want to show just MCP tracks in KK Mixer View (param) must be (true) + int numTracks = CSurf_NumTracks(false); if ((newBankStart < 0) || (newBankStart > numTracks)) { return; } @@ -632,7 +644,27 @@ class NiMidiSurface: public BaseSurface { CSurf_SetSurfacePan(track, CSurf_OnPanChange(track, dvalue * 0.00098425, true), nullptr); } - // ToDo: toggle mute and solo for the selected track + void _onSelTrackMute() { + if (g_trackInFocus > 0) { + MediaTrack* track = CSurf_TrackFromID(g_trackInFocus, false); + if (!track) { + return; + } + bool muted = *(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr); + CSurf_OnMuteChange(track, muted ? 0 : 1); + } + } + + void _onSelTrackSolo() { + if (g_trackInFocus > 0) { + MediaTrack* track = CSurf_TrackFromID(g_trackInFocus, false); + if (!track) { + return; + } + int soloState = *(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr); + CSurf_OnSoloChange(track, soloState == 0 ? 1 : 0); // ToDo: Consider settings solo state to 2 (soloed in place) + } + } // ToDo: toggle automation mode From a966ed4e04111bf0f8c22f31fd32f09d9950c316 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 27 May 2019 01:02:44 -0700 Subject: [PATCH 39/47] Track names, master track, bank button lights - Track name changes get reflected via callback - Master track indicated in Mixer View (no mute or solo on Master) - Bank slect button light --- src/niMidi.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index f6e7bc6..35d451f 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -83,7 +83,7 @@ const unsigned char CMD_SEL_TRACK_AVAILABLE = 0x68; // ToDo: ? const unsigned char CMD_SEL_TRACK_MUTED_BY_SOLO = 0x69; // ToDo: ? const unsigned char TRTYPE_UNSPEC = 1; -const unsigned char TRTYPE_MASTER = 6; // ToDo: consider declaring master track in Mixer View +const unsigned char TRTYPE_MASTER = 6; // State Information // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration @@ -192,10 +192,6 @@ class NiMidiSurface: public BaseSurface { } // ToDo: add more button lights: METRO, AUTO, tbd: undo/redo, clear, quantize, tempo - // ToDo: bank buttons - - // ToDo: If track name changes the mixer view shall be updated "immediately". This is currently not captured via SetTrackList change! - // We may have to poll for such a change - best candidate would be SetSurfaceSelected..... virtual void SetTrackListChange() override { // If tracklist changes update Mixer View and ensure sanity of track and bank focus @@ -230,8 +226,13 @@ class NiMidiSurface: public BaseSurface { int oldBankStart = this->_bankStart; this->_bankStart = id - numInBank; if (this->_bankStart != oldBankStart) { + // Update everything this->_allMixerUpdate(); } + else { + // Update track names + this->_namesMixerUpdate(); + } // Let Keyboard know about changed track selection this->_sendSysex(CMD_TRACK_SELECTED, 1, numInBank); // Set KK Instance Focus @@ -490,21 +491,37 @@ class NiMidiSurface: public BaseSurface { int numInBank = 0; int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; // avoid ambiguity: track counting always zero based int numTracks = CSurf_NumTracks(false); + // Set bank select button lights + int bankLights = 3; // left and right on + if (numTracks < BANK_NUM_TRACKS) { + bankLights = 0; // left and right off + } + else if (this->_bankStart == 0) { + bankLights = 2; // left off, right on + } + else if (bankEnd >= numTracks) { + bankLights = 1; // left on, right off + } + this->_sendCc(CMD_NAV_BANKS, bankLights); if (bankEnd > numTracks) { bankEnd = numTracks; // Mark additional bank tracks as not available int lastInLastBank = numTracks % BANK_NUM_TRACKS; for (int i = 7; i > lastInLastBank; --i) { - this->_sendSysex(CMD_TRACK_AVAIL, 0, i); + this->_sendSysex(CMD_TRACK_AVAIL, 0, i); } } for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { MediaTrack* track = CSurf_TrackFromID(id, false); if (!track) { break; + } + if (id == 0) { + this->_sendSysex(CMD_TRACK_AVAIL, TRTYPE_MASTER, numInBank); } - // ToDo: Consider indicating master track - this->_sendSysex(CMD_TRACK_AVAIL, TRTYPE_UNSPEC, numInBank); + else { + this->_sendSysex(CMD_TRACK_AVAIL, TRTYPE_UNSPEC, numInBank); + } int selected = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr); this->_sendSysex(CMD_TRACK_SELECTED, selected, numInBank); int soloState = *(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr); @@ -531,6 +548,26 @@ class NiMidiSurface: public BaseSurface { } } + void _namesMixerUpdate() { + int numInBank = 0; + int bankEnd = this->_bankStart + BANK_NUM_TRACKS - 1; // avoid ambiguity: track counting always zero based + int numTracks = CSurf_NumTracks(false); + if (bankEnd > numTracks) { + bankEnd = numTracks; + } + for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { + MediaTrack* track = CSurf_TrackFromID(id, false); + if (!track) { + break; + } + char* name = (char*)GetSetMediaTrackInfo(track, "P_NAME", nullptr); + if (!name) { + name = ""; + } + this->_sendSysex(CMD_TRACK_NAME, 0, numInBank, name); + } + } + void _clearAllSelectedTracks() { // Clear all selected tracks. Copyright (c) 2010 and later Tim Payne (SWS) int iSel = 0; From 7f3229da37ae8a67562ba07fe8d3c999e092bc7d Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 27 May 2019 12:15:30 -0700 Subject: [PATCH 40/47] Track(s) muted by solo implemented - implemented in plugin by supressing peaks because KK firmware does not use this command at all (yet?) - maybe some newer firmware version of KK will do so --- src/niMidi.cpp | 74 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 35d451f..c1ab766 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -54,7 +54,7 @@ const unsigned char CMD_TRACK_VOLUME_TEXT = 0x46; const unsigned char CMD_TRACK_PAN_TEXT = 0x47; const unsigned char CMD_TRACK_NAME = 0x48; const unsigned char CMD_TRACK_VU = 0x49; -const unsigned char CMD_TRACK_MUTED_BY_SOLO = 0x4A; // ToDo: Implement this in _allMixerUpdate AND _SetSurfaceSolo (or less efficiently _peakMixerUpdate) +const unsigned char CMD_TRACK_MUTED_BY_SOLO = 0x4A; // ToDo: Check back with newer KK firmware if this gets implemented correctly const unsigned char CMD_KNOB_VOLUME0 = 0x50; const unsigned char CMD_KNOB_VOLUME1 = 0x51; const unsigned char CMD_KNOB_VOLUME2 = 0x52; @@ -71,7 +71,7 @@ const unsigned char CMD_KNOB_PAN4 = 0x5c; const unsigned char CMD_KNOB_PAN5 = 0x5d; const unsigned char CMD_KNOB_PAN6 = 0x5e; const unsigned char CMD_KNOB_PAN7 = 0x5f; -const unsigned char CMD_PLAY_CLIP = 0x60; // not used +const unsigned char CMD_PLAY_CLIP = 0x60; // ToDo: Use this to switch Mixer view into bank with focused = selected track!! const unsigned char CMD_STOP_CLIP = 0x61; // not used const unsigned char CMD_PLAY_SCENE = 0x62; // not used const unsigned char CMD_RECORD_SESSION = 0x63; // not used @@ -88,6 +88,8 @@ const unsigned char TRTYPE_MASTER = 6; // State Information // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; +static bool g_anySolo = false; +static int g_soloStateBank[BANK_NUM_TRACKS] = { 0 }; signed char convertSignedMidiValue(unsigned char value) { // Convert a signed 7 bit MIDI value to a signed char. @@ -278,10 +280,9 @@ class NiMidiSurface: public BaseSurface { virtual void SetSurfaceSolo(MediaTrack* track, bool solo) override { // Note: Solo in Reaper can have different meanings (Solo In Place, Solo In Front and much more -> Reaper Preferences) int id = CSurf_TrackToID(track, false); - // Reaper API has a "special" behavior: https://forum.cockos.com/showthread.php?p=2139125#post2139125 - // Ignore solo on master - // ToDo: Consider implementing MUTED_BY_SOLO here together with a complete solo status update for the bank! + // Ignore solo an master, this is only used as an "any track is soloed" indicator if (id == 0) { + g_anySolo = solo; return; } if (id == g_trackInFocus) { @@ -289,7 +290,16 @@ class NiMidiSurface: public BaseSurface { } if ((id >= this->_bankStart) && (id < this->_bankStart + BANK_NUM_TRACKS)) { int numInBank = id % BANK_NUM_TRACKS; - this->_sendSysex(CMD_TRACK_SOLOED, solo ? 1 : 0, numInBank); + if (solo) { + g_soloStateBank[numInBank] = 1; + this->_sendSysex(CMD_TRACK_SOLOED, 1, numInBank); + this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, 0, numInBank); + } + else { + g_soloStateBank[numInBank] = 0; + this->_sendSysex(CMD_TRACK_SOLOED, 0, numInBank); + this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, g_anySolo ? 1 : 0, numInBank); + } } } @@ -320,7 +330,8 @@ class NiMidiSurface: public BaseSurface { << (int)event->midi_message[1] << " " << (int)event->midi_message[2] << " Focus Track " << g_trackInFocus << " Bank Start " - << this->_bankStart << endl; + << this->_bankStart << " anySolo " + << g_anySolo << endl; ShowConsoleMsg(s.str().c_str()); #endif @@ -466,18 +477,35 @@ class NiMidiSurface: public BaseSurface { break; } j = 2 * numInBank; - // Muted tracks still report peak levels => ignore these if the track is not soloed - // ToDo: Consider to keep track of mute and solo states (of tracks in bank) locally in plugin - saves CPU - if ((*(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr)) && - (*(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr) == 0)) { - peakBank[j] = 1; - peakBank[j + 1] = 1; + + // ToDo: Check with KK firmware improvements if CMD_TRACK_MUTED_BY_SOLO will eventually be implemented correctly. + // In that case the meter bars should be grey and we do not have to supress them entirely as implemented now. + + // If any track is soloed then only soloed tracks and the master show peaks (irrespective of their mute state) + if (g_anySolo) { + if ((g_soloStateBank[numInBank] == 0) && (numInBank != 0)) { + peakBank[j] = 1; + peakBank[j + 1] = 1; + } + else { + peakValue = Track_GetPeakInfo(track, 0); // left channel + peakBank[j] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 + peakValue = Track_GetPeakInfo(track, 1); // right channel + peakBank[j + 1] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 + } } + // If no tracks are soloed then muted tracks shall show no peaks else { - peakValue = Track_GetPeakInfo(track, 0); // left channel - peakBank[j] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 - peakValue = Track_GetPeakInfo(track, 1); // right channel - peakBank[j + 1] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 + if (*(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr)) { + peakBank[j] = 1; + peakBank[j + 1] = 1; + } + else { + peakValue = Track_GetPeakInfo(track, 0); // left channel + peakBank[j] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 + peakValue = Track_GetPeakInfo(track, 1); // right channel + peakBank[j + 1] = volToChar_KkMk2(peakValue); // returns value between 1 and 127 + } } } this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); @@ -512,6 +540,7 @@ class NiMidiSurface: public BaseSurface { } } for (int id = this->_bankStart; id <= bankEnd; ++id, ++numInBank) { + // ToDo: While not really necessary consider removing some info polls for master track (i.e. record arm, solo) MediaTrack* track = CSurf_TrackFromID(id, false); if (!track) { break; @@ -525,7 +554,16 @@ class NiMidiSurface: public BaseSurface { int selected = *(int*)GetSetMediaTrackInfo(track, "I_SELECTED", nullptr); this->_sendSysex(CMD_TRACK_SELECTED, selected, numInBank); int soloState = *(int*)GetSetMediaTrackInfo(track, "I_SOLO", nullptr); - this->_sendSysex(CMD_TRACK_SOLOED, (soloState==0) ? 0 : 1, numInBank); + if (soloState == 0) { + g_soloStateBank[numInBank] = 0; + this->_sendSysex(CMD_TRACK_SOLOED, 0, numInBank); + this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, g_anySolo ? 1 : 0, numInBank); + } + else { + g_soloStateBank[numInBank] = 1; + this->_sendSysex(CMD_TRACK_SOLOED, 1, numInBank); + this->_sendSysex(CMD_SEL_TRACK_MUTED_BY_SOLO, 0, numInBank); + } bool muted = *(bool*)GetSetMediaTrackInfo(track, "B_MUTE", nullptr); this->_sendSysex(CMD_TRACK_MUTED, muted ? 1 : 0, numInBank); int armed = *(int*)GetSetMediaTrackInfo(track, "I_RECARM", nullptr); From 2c2a2ab7497207091047da611df8fc057662e392 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 27 May 2019 14:50:23 -0700 Subject: [PATCH 41/47] Push Encoder to refocus bank --- src/niMidi.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index c1ab766..94c6e8c 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -2,8 +2,9 @@ * ReaKontrol * Support for MIDI protocol used by Komplete Kontrol S-series Mk2, A-series * and M-Series - * Author: James Teh + * Authors: James Teh , Leonard de Ruijter, brumbear@pacificpeaks * Copyright 2018-2019 James Teh + * Copyright 2019 Pacific Peaks Studio * License: GNU General Public License version 2.0 */ @@ -71,7 +72,7 @@ const unsigned char CMD_KNOB_PAN4 = 0x5c; const unsigned char CMD_KNOB_PAN5 = 0x5d; const unsigned char CMD_KNOB_PAN6 = 0x5e; const unsigned char CMD_KNOB_PAN7 = 0x5f; -const unsigned char CMD_PLAY_CLIP = 0x60; // ToDo: Use this to switch Mixer view into bank with focused = selected track!! +const unsigned char CMD_PLAY_CLIP = 0x60; // Use here to switch Mixer view to the bank containing the currently focused (= selected) track const unsigned char CMD_STOP_CLIP = 0x61; // not used const unsigned char CMD_PLAY_SCENE = 0x62; // not used const unsigned char CMD_RECORD_SESSION = 0x63; // not used @@ -424,6 +425,11 @@ class NiMidiSurface: public BaseSurface { case CMD_KNOB_PAN7: this->_onKnobPanChange(command, convertSignedMidiValue(value)); break; + case CMD_PLAY_CLIP: + // We use this for a different purpose: switch Mixer view to the bank containing the currently focused (= selected) track + this->_bankStart = (int)(g_trackInFocus / BANK_NUM_TRACKS) * BANK_NUM_TRACKS; + this->_allMixerUpdate(); + break; case CMD_CHANGE_SEL_TRACK_VOLUME: this->_onSelTrackVolumeChange(convertSignedMidiValue(value)); break; From 547566bfe753cd4e4ce48069fd2da096e99d3d69 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 27 May 2019 14:54:47 -0700 Subject: [PATCH 42/47] Update readme.md --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 5ab50b9..fccfcb0 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,9 @@ -# ReaKontrol +# ReaKontrol-Fork - Fork of the excellent ReaKontrol repository published by James Teh: https://github.com/jcsteh/reaKontrol -- Author: brumbear@pacificpeaks & other contributors -- Copyright: 2019 and later Pacific Peaks Studio, see individual copyrights in source code +- Fork Author: brumbear@pacificpeaks +- Fork Copyright: 2019 Pacific Peaks Studio, see individual copyrights in source code - License: GNU General Public License version 2.0. -- License Notes: As the original work is published under GPLv2 the updated programs are also licensed under GPLv2. May be updated to GPLv3 if copyright holder of original work agrees to update too. +- License Notes: As the original work is published under GPLv2 the modified programs are also licensed under GPLv2. May be updated to GPLv3 if copyright holder of original work agrees to update too. ## Feature Integration & Releases This fork is mainly a development branch aiming to continuously add functionality to ReaKontrol. From e71fc666a4c0455fda6c9d3061c3de4aeb8132be Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 27 May 2019 19:17:49 -0700 Subject: [PATCH 43/47] Edit a few comments --- src/niMidi.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 94c6e8c..ff50e60 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -72,7 +72,7 @@ const unsigned char CMD_KNOB_PAN4 = 0x5c; const unsigned char CMD_KNOB_PAN5 = 0x5d; const unsigned char CMD_KNOB_PAN6 = 0x5e; const unsigned char CMD_KNOB_PAN7 = 0x5f; -const unsigned char CMD_PLAY_CLIP = 0x60; // Use here to switch Mixer view to the bank containing the currently focused (= selected) track +const unsigned char CMD_PLAY_CLIP = 0x60; // Used here to switch Mixer view to the bank containing the currently focused (= selected) track const unsigned char CMD_STOP_CLIP = 0x61; // not used const unsigned char CMD_PLAY_SCENE = 0x62; // not used const unsigned char CMD_RECORD_SESSION = 0x63; // not used @@ -203,7 +203,7 @@ class NiMidiSurface: public BaseSurface { if (g_trackInFocus > numTracks) { g_trackInFocus = numTracks; // Unfortunately we cannot afford to explicitly select the last track automatically because this could screw up - // running actions or macros. The plugin may not manipulate track selection without the user deliberately triggering + // running actions or macros. The plugin must not manipulate track selection without the user deliberately triggering // track selection/navigation on the keyboard (or from within Reaper). } // Protect against loosing bank focus. Set focus on last bank in this case. @@ -281,7 +281,7 @@ class NiMidiSurface: public BaseSurface { virtual void SetSurfaceSolo(MediaTrack* track, bool solo) override { // Note: Solo in Reaper can have different meanings (Solo In Place, Solo In Front and much more -> Reaper Preferences) int id = CSurf_TrackToID(track, false); - // Ignore solo an master, this is only used as an "any track is soloed" indicator + // Ignore solo on master, id = 0 is only used as an "any track is soloed" indicator if (id == 0) { g_anySolo = solo; return; From 3d3c13a67731150043d036f7a71c8202918395ba Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Mon, 27 May 2019 22:36:08 -0700 Subject: [PATCH 44/47] Metronome Button Light Working --- src/main.cpp | 9 +++++++++ src/mcu.cpp | 4 ++++ src/niMidi.cpp | 21 ++++++++++++++++++++- src/reaKontrol.h | 6 ++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 6708378..761f087 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -145,7 +145,16 @@ BaseSurface::~BaseSurface() { } void BaseSurface::Run() { + const int METRO_UPDATE = 30; + static int simpleTimer = 0; this->_peakMixerUpdate(); // ToDo: Maybe update only every 2nd call to save CPU? + if (simpleTimer < METRO_UPDATE) { + ++simpleTimer; + } + else { + simpleTimer = 0; + this->_metronomeUpdate(); + } if (!this->_midiIn) { return; } diff --git a/src/mcu.cpp b/src/mcu.cpp index 95d074b..5a247d0 100644 --- a/src/mcu.cpp +++ b/src/mcu.cpp @@ -107,6 +107,10 @@ class McuSurface: public BaseSurface { // DUMMY. Currently only implemented for niMiDi (Mk2). Maybe better to move Run() callback into niMidi.cpp and mcu.cpp? } + void _metronomeUpdate() override { + // DUMMY. Currently only implemented for niMiDi (Mk2). Maybe better to move Run() callback into niMidi.cpp and mcu.cpp? + } + private: void _sendRaw(const string& message) { // MIDI_event_t includes 4 bytes for the message, but we need more. diff --git a/src/niMidi.cpp b/src/niMidi.cpp index ff50e60..e9aaf33 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -194,7 +194,7 @@ class NiMidiSurface: public BaseSurface { } } - // ToDo: add more button lights: METRO, AUTO, tbd: undo/redo, clear, quantize, tempo + // ToDo: add more button lights: AUTO, tbd: undo/redo, clear, quantize, tempo virtual void SetTrackListChange() override { // If tracklist changes update Mixer View and ensure sanity of track and bank focus @@ -517,6 +517,25 @@ class NiMidiSurface: public BaseSurface { this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); } + // Copyright (c) 2010 and later Tim Payne (SWS), Jeffos + void* GetConfigVar(const char* cVar) { + int sztmp; + void* p = NULL; + if (int iOffset = projectconfig_var_getoffs(cVar, &sztmp)) + { + p = projectconfig_var_addr(EnumProjects(-1, NULL, 0), iOffset); + } + else + { + p = get_config_var(cVar, &sztmp); + } + return p; + } + + void _metronomeUpdate() override { + this->_sendCc(CMD_METRO, (*(int*)GetConfigVar("projmetroen") & 1)); + } + private: int _protocolVersion = 0; int _bankStart = -1; diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 0fe11b8..273f393 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -52,6 +52,11 @@ #define REAPERAPI_WANT_Track_GetPeakInfo #define REAPERAPI_WANT_mkvolstr #define REAPERAPI_WANT_mkpanstr +#define REAPERAPI_WANT_get_config_var +#define REAPERAPI_WANT_projectconfig_var_getoffs +#define REAPERAPI_WANT_projectconfig_var_addr +#define REAPERAPI_WANT_EnumProjects + #include #include @@ -73,6 +78,7 @@ class BaseSurface: public IReaperControlSurface { midi_Output* _midiOut = nullptr; virtual void _onMidiEvent(MIDI_event_t* event) = 0; virtual void _peakMixerUpdate() = 0; + virtual void _metronomeUpdate() = 0; }; IReaperControlSurface* createNiMidiSurface(int inDev, int outDev); From fcf874073d712dd8560dbd8e7aa92f77b5678ba8 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 28 May 2019 10:39:55 -0700 Subject: [PATCH 45/47] ToDo comments updated - future roadmap --- src/main.cpp | 3 +++ src/niMidi.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 761f087..0e886a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -145,6 +145,8 @@ BaseSurface::~BaseSurface() { } void BaseSurface::Run() { + // ToDo: Rather than putting this in BaseSurface consider to just override Run() in NiMidiSurface and call the superclass : BaseSurface::Run() + // ToDo: Metronome: try to substitute polling with callback, see details @ _metronomeUpdate in NiMiDi.cpp const int METRO_UPDATE = 30; static int simpleTimer = 0; this->_peakMixerUpdate(); // ToDo: Maybe update only every 2nd call to save CPU? @@ -155,6 +157,7 @@ void BaseSurface::Run() { simpleTimer = 0; this->_metronomeUpdate(); } + // -------------------------------------------------------------------------------- if (!this->_midiIn) { return; } diff --git a/src/niMidi.cpp b/src/niMidi.cpp index e9aaf33..4e5be02 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -532,6 +532,8 @@ class NiMidiSurface: public BaseSurface { return p; } + // ToDo: try to replace this whole polling scheme with CSURF_EXT_SETMETRONOME 0x00010002 // parm1=0 <-> disable, !0 <-> enable + // Ideally this should work as a callback, i.e. interrupt driven rather than polling void _metronomeUpdate() override { this->_sendCc(CMD_METRO, (*(int*)GetConfigVar("projmetroen") & 1)); } From 3d1d16030fbb672053aaca2759c90f9fc7305033 Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Tue, 28 May 2019 22:37:07 -0700 Subject: [PATCH 46/47] Update readme.md --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index fccfcb0..e5b0255 100644 --- a/readme.md +++ b/readme.md @@ -6,19 +6,19 @@ - License Notes: As the original work is published under GPLv2 the modified programs are also licensed under GPLv2. May be updated to GPLv3 if copyright holder of original work agrees to update too. ## Feature Integration & Releases -This fork is mainly a development branch aiming to continuously add functionality to ReaKontrol. +This fork is mainly a bleeding edge development branch aiming to continuously add functionality to ReaKontrol. - New features will be merged continously into this fork's master @ https://github.com/brummbrum/reaKontrol - Testing and calibration is done with NI S-Series Mk2 Komplete Kontrol Keyboard. -- When a critcial amount of functionality has been reached there will be releases in a dedicated release branch. -- Binaries are planned to be available for releases (Windows only) +- When a critcial amount of functionality has been reached there will be stable releases in a dedicated release branch. +- Binaries are available for releases (Windows only): https://github.com/brummbrum/reaKontrol/releases ## Upstream Integration into Parent Repository - New features are also offered to the upstream repository @ https://github.com/jcsteh/reaKontrol - Some updated features may or may not be merged with the upstream master due to possibly different priorities - The sequence in which new features are published may be different between this fork and the upstream repository due to possibly different priorities -## Which fork should you use? -- To the extent that features are integrated into the upstream master it is recommended to stick to the parent repository @ https://github.com/jcsteh/reaKontrol +## Which branch should you follow? +- To the extent that features are integrated into the upstream master it is recommended to stick to the parent repository @ https://github.com/jcsteh/reaKontrol. The parent repository also takes into account dependencies to OSARA relevant for users using screen readers. ## Build instructions and additional description - Please refer to the parent repository @ https://github.com/jcsteh/reaKontrol From f2fa673860aae78c2766b92ff35d1e219266c63a Mon Sep 17 00:00:00 2001 From: brummbrum <48608100+brummbrum@users.noreply.github.com> Date: Sat, 8 Jun 2019 01:44:57 -0700 Subject: [PATCH 47/47] Metronome button light via callback * Use extended callback function to get event driven metronome update * Poll the metronome when project tab changes --- src/main.cpp | 8 +++---- src/mcu.cpp | 4 ---- src/niMidi.cpp | 57 +++++++++++++++++++++++++++++------------------- src/reaKontrol.h | 1 - 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0e886a9..29fd859 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -146,16 +146,14 @@ BaseSurface::~BaseSurface() { void BaseSurface::Run() { // ToDo: Rather than putting this in BaseSurface consider to just override Run() in NiMidiSurface and call the superclass : BaseSurface::Run() - // ToDo: Metronome: try to substitute polling with callback, see details @ _metronomeUpdate in NiMiDi.cpp - const int METRO_UPDATE = 30; + const int METER_UPDATE = 0; // Update on every call for now (approx. 30/s) static int simpleTimer = 0; - this->_peakMixerUpdate(); // ToDo: Maybe update only every 2nd call to save CPU? - if (simpleTimer < METRO_UPDATE) { + if (simpleTimer < METER_UPDATE) { ++simpleTimer; } else { simpleTimer = 0; - this->_metronomeUpdate(); + this->_peakMixerUpdate(); } // -------------------------------------------------------------------------------- if (!this->_midiIn) { diff --git a/src/mcu.cpp b/src/mcu.cpp index 5a247d0..95d074b 100644 --- a/src/mcu.cpp +++ b/src/mcu.cpp @@ -107,10 +107,6 @@ class McuSurface: public BaseSurface { // DUMMY. Currently only implemented for niMiDi (Mk2). Maybe better to move Run() callback into niMidi.cpp and mcu.cpp? } - void _metronomeUpdate() override { - // DUMMY. Currently only implemented for niMiDi (Mk2). Maybe better to move Run() callback into niMidi.cpp and mcu.cpp? - } - private: void _sendRaw(const string& message) { // MIDI_event_t includes 4 bytes for the message, but we need more. diff --git a/src/niMidi.cpp b/src/niMidi.cpp index 4e5be02..6a8fdbf 100644 --- a/src/niMidi.cpp +++ b/src/niMidi.cpp @@ -86,6 +86,8 @@ const unsigned char CMD_SEL_TRACK_MUTED_BY_SOLO = 0x69; // ToDo: ? const unsigned char TRTYPE_UNSPEC = 1; const unsigned char TRTYPE_MASTER = 6; +#define CSURF_EXT_SETMETRONOME 0x00010002 + // State Information // ToDo: Rather than using glocal variables consider moving these into the BaseSurface class declaration static int g_trackInFocus = 0; @@ -222,7 +224,9 @@ class NiMidiSurface: public BaseSurface { // Using SetSurfaceSelected() rather than OnTrackSelection(): // SetSurfaceSelected() is less economical because it will be called multiple times (also for unselecting tracks). // However, SetSurfaceSelected() is the more robust choice because of: https://forum.cockos.com/showpost.php?p=2138446&postcount=15 - // See also notes in SetSurfaceRecArm + + // Note: SetSurfaceSelected is also called on project tab change + this->_metronomeUpdate(); //check if metronome status has changed when switching project tabs if (selected) { int id = CSurf_TrackToID(track, false); int numInBank = id % BANK_NUM_TRACKS; @@ -241,7 +245,7 @@ class NiMidiSurface: public BaseSurface { // Set KK Instance Focus g_trackInFocus = id; this->_sendSysex(CMD_SET_KK_INSTANCE, 0, 0, - getKkInstanceName(track)); + getKkInstanceName(track)); } } @@ -316,10 +320,19 @@ class NiMidiSurface: public BaseSurface { } } + virtual int Extended(int call, void *parm1, void *parm2, void *parm3) override { + if (call != CSURF_EXT_SETMETRONOME) { + return 0; // we are only interested in the metronome. Note: This works fine but does not update the status when changing project tabs + } + this->_sendCc(CMD_METRO, (parm1 == 0) ? 0 : 1); + return 1; + } + protected: void _onMidiEvent(MIDI_event_t* event) override { if (event->midi_message[0] != MIDI_CC) { return; + // ToDo: Analyze other incoming MIDI messages too, like Sysex, MCU etc } unsigned char& command = event->midi_message[1]; unsigned char& value = event->midi_message[2]; @@ -517,27 +530,6 @@ class NiMidiSurface: public BaseSurface { this->_sendSysex(CMD_TRACK_VU, 2, 0, peakBank); } - // Copyright (c) 2010 and later Tim Payne (SWS), Jeffos - void* GetConfigVar(const char* cVar) { - int sztmp; - void* p = NULL; - if (int iOffset = projectconfig_var_getoffs(cVar, &sztmp)) - { - p = projectconfig_var_addr(EnumProjects(-1, NULL, 0), iOffset); - } - else - { - p = get_config_var(cVar, &sztmp); - } - return p; - } - - // ToDo: try to replace this whole polling scheme with CSURF_EXT_SETMETRONOME 0x00010002 // parm1=0 <-> disable, !0 <-> enable - // Ideally this should work as a callback, i.e. interrupt driven rather than polling - void _metronomeUpdate() override { - this->_sendCc(CMD_METRO, (*(int*)GetConfigVar("projmetroen") & 1)); - } - private: int _protocolVersion = 0; int _bankStart = -1; @@ -768,6 +760,25 @@ class NiMidiSurface: public BaseSurface { } } + void* GetConfigVar(const char* cVar) { // Copyright (c) 2010 and later Tim Payne (SWS), Jeffos + int sztmp; + void* p = NULL; + if (int iOffset = projectconfig_var_getoffs(cVar, &sztmp)) + { + p = projectconfig_var_addr(EnumProjects(-1, NULL, 0), iOffset); + } + else + { + p = get_config_var(cVar, &sztmp); + } + return p; + } + + void _metronomeUpdate() { + // Actively poll the metronome status on project tab changes + this->_sendCc(CMD_METRO, (*(int*)GetConfigVar("projmetroen") & 1)); + } + // ToDo: toggle automation mode void _sendCc(unsigned char command, unsigned char value) { diff --git a/src/reaKontrol.h b/src/reaKontrol.h index 273f393..c629ed2 100644 --- a/src/reaKontrol.h +++ b/src/reaKontrol.h @@ -78,7 +78,6 @@ class BaseSurface: public IReaperControlSurface { midi_Output* _midiOut = nullptr; virtual void _onMidiEvent(MIDI_event_t* event) = 0; virtual void _peakMixerUpdate() = 0; - virtual void _metronomeUpdate() = 0; }; IReaperControlSurface* createNiMidiSurface(int inDev, int outDev);