Skip to content

Commit 85e4c86

Browse files
tucktuckg00seclaude
andcommitted
v0.9.0: NRPN MIDI controller routing, SET popup, state v19
New features: - NRPN-based slice editing from hardware MIDI controllers: zoom (NRPN 8193), slice start (8194), slice end (8195) via CC 96/97 relative data bytes - SET button popup replaces standalone SettingsPanel; includes NRPN enable, consume-CCs toggle, and MIDI channel filter - Live slice boundary preview during NRPN knob gesture; auto-commits ~300ms after knob stops moving - NRPN disabled by default; note added to README about controller requirements and Ableton Live limitation (CC 96/97 intercepted by Ableton before plugin) Breaking change: - State format bumped to v19; presets saved with earlier versions will not load Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d2686fa commit 85e4c86

17 files changed

Lines changed: 777 additions & 148 deletions

.github/workflows/release.yml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ jobs:
1313
with:
1414
submodules: recursive
1515

16+
- name: Get version
17+
shell: bash
18+
run: echo "PLUGIN_VERSION=$(echo '${{ github.ref_name }}' | sed 's/^v//')" >> $GITHUB_ENV
19+
1620
- name: Configure
17-
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
21+
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DINTERSECT_VERSION=${{ env.PLUGIN_VERSION }}
1822

1923
- name: Build
2024
run: cmake --build build --config Release
@@ -43,8 +47,12 @@ jobs:
4347
with:
4448
submodules: recursive
4549

50+
- name: Get version
51+
shell: bash
52+
run: echo "PLUGIN_VERSION=$(echo '${{ github.ref_name }}' | sed 's/^v//')" >> $GITHUB_ENV
53+
4654
- name: Configure
47-
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
55+
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DINTERSECT_VERSION=${{ env.PLUGIN_VERSION }}
4856

4957
- name: Build
5058
run: cmake --build build --config Release
@@ -68,8 +76,12 @@ jobs:
6876
with:
6977
submodules: recursive
7078

79+
- name: Get version
80+
shell: bash
81+
run: echo "PLUGIN_VERSION=$(echo '${{ github.ref_name }}' | sed 's/^v//')" >> $GITHUB_ENV
82+
7183
- name: Configure
72-
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
84+
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DINTERSECT_VERSION=${{ env.PLUGIN_VERSION }}
7385

7486
- name: Build
7587
run: cmake --build build --config Release
@@ -94,8 +106,12 @@ jobs:
94106
with:
95107
submodules: recursive
96108

109+
- name: Get version
110+
shell: bash
111+
run: echo "PLUGIN_VERSION=$(echo '${{ github.ref_name }}' | sed 's/^v//')" >> $GITHUB_ENV
112+
97113
- name: Configure
98-
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
114+
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DINTERSECT_VERSION=${{ env.PLUGIN_VERSION }}
99115

100116
- name: Build
101117
run: cmake --build build --config Release

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
cmake_minimum_required(VERSION 3.22)
2-
project(Intersect VERSION 1.0.0)
2+
set(INTERSECT_VERSION "0.0.0" CACHE STRING "Plugin version (injected by CI)")
3+
project(Intersect)
34

45
set(CMAKE_CXX_STANDARD 20)
56
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -54,6 +55,7 @@ juce_add_plugin(Intersect
5455
PLUGIN_CODE Ixsr
5556
FORMATS VST3 AU Standalone
5657
PRODUCT_NAME "INTERSECT"
58+
VERSION "${INTERSECT_VERSION}"
5759
IS_SYNTH TRUE
5860
NEEDS_MIDI_INPUT TRUE
5961
NEEDS_MIDI_OUTPUT TRUE

README.md

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ INTERSECT is a sample slicer instrument plugin (VST3/AU/Standalone) with per-sli
1010
- [Installation](#installation)
1111
- [Workflow Basics](#workflow-basics)
1212
- [Controls and Shortcuts Reference](#controls-and-shortcuts-reference)
13+
- [MIDI Controller Routing (NRPN)](#midi-controller-routing-nrpn)
1314
- [Theme Customization](#theme-customization)
1415
- [Build from Source](#build-from-source)
1516
- [Dependencies](#dependencies)
@@ -80,7 +81,7 @@ xattr -cr /Applications/INTERSECT.app
8081
| --- | --- | --- |
8182
| `BPM` | Sample default BPM | Drag up/down, double-click to type |
8283
| `SET BPM` | Calculate BPM from duration menu | 16 bars to 1/16 note |
83-
| `PITCH` | Semitone shift | `-24` to `+24` |
84+
| `PITCH` | Semitone shift | `-48` to `+48` |
8485
| `TUNE` | Fine detune | `-100` to `+100` cents |
8586
| `ALGO` | Algorithm selector | `Repitch`, `Stretch`, `Bungee` |
8687
| `TONAL` | Tonality limit (Stretch only) | `0` to `8000` Hz |
@@ -99,7 +100,7 @@ xattr -cr /Applications/INTERSECT.app
99100
| `LOAD` | Open file chooser | Replaces current sample |
100101
| `PANIC` | Kill active voices immediately | Also stops lazy chop |
101102
| `UNDO / REDO` | History navigation | Buttons in header |
102-
| `UI` | Theme and UI scale popup | Theme chooser and `+/- 0.25` scale |
103+
| `SET` | Theme, scale, and NRPN popup | Theme chooser, `+/- 0.25` scale, and NRPN settings |
103104
| Sample info text | Load/relink shortcut | Click sample name area to load; missing file text opens relink dialog |
104105

105106
### Slice Control Bar (selected slice)
@@ -178,6 +179,44 @@ Requires a selected slice before opening.
178179

179180
Single-letter action shortcuts are intentionally unbound so DAW keyboard-MIDI note entry remains available.
180181

182+
## MIDI Controller Routing (NRPN)
183+
184+
INTERSECT supports NRPN-based slice editing from a hardware or software MIDI controller. Enable it via the **SET** button in the header bar — the popup contains an NRPN section with channel and consume options.
185+
186+
**Controller requirements:** The controller must support NRPN mode with configurable MSB/LSB address values, and must send **relative** data bytes — CC 96 (Data Increment) and CC 97 (Data Decrement) — rather than absolute CC 6 (Data Entry). Most dedicated encoder-style controllers (e.g. Korg nanoKONTROL in NRPN mode, MIDI Fighter Twister) support this. Simple knob controllers that only send CC 6 absolute values will not work.
187+
188+
Select the slice to edit by enabling **FM** (Follow MIDI) and playing its MIDI note. Then use the start/end knobs to adjust the slice boundaries. Commit happens automatically ~300ms after the knob stops moving — the same feel as releasing a parameter slider. Zoom in for finer control; each knob step moves `viewWidth / 16383` samples.
189+
190+
### NRPN Parameter Table
191+
192+
NRPN numbers use CC 99 (MSB address) / CC 98 (LSB address) to select the parameter, then CC 96 (Data Increment) or CC 97 (Data Decrement) to send a ±1 step. No absolute-value data bytes (CC 6/38) are used.
193+
194+
When programming a hardware controller, set the knob to **NRPN mode** with **MSB 64** and the LSB from the table below.
195+
196+
| NRPN | MSB | LSB | Name | Direction | Notes |
197+
| --- | --- | --- | --- | --- | --- |
198+
| 8193 | 64 | 1 | Zoom | CC 96 / CC 97 | Zoom in / out |
199+
| 8194 | 64 | 2 | Slice start | CC 96 / CC 97 | Each step = viewWidth / 16383 samples |
200+
| 8195 | 64 | 3 | Slice end | CC 96 / CC 97 | Each step = viewWidth / 16383 samples |
201+
202+
### SET Button — NRPN Settings
203+
204+
Open with the **SET** button in the header bar.
205+
206+
| Control | Function |
207+
| --- | --- |
208+
| `NRPN` | Enable/disable NRPN slice editing |
209+
| `CONSUME CCs` | Strip NRPN edit CCs from MIDI output so they don't reach downstream instruments |
210+
| `CH −` / `CH +` | MIDI channel filter (0 = omni) |
211+
212+
Settings are saved to the INTERSECT settings file alongside theme and UI scale.
213+
214+
### DAW-specific notes
215+
216+
**Ableton Live:** NRPN control does not work in Ableton Live. Ableton intercepts the CC 96/97 data-increment bytes before they reach the plugin, so the plugin only ever sees the address bytes (CC 98/99) and can never fire an event. This is an Ableton routing limitation with no known workaround on the plugin side.
217+
218+
**REAPER / Bitwig:** Route a MIDI track to the plugin instance as normal. No additional configuration is needed; all CC messages are forwarded to the plugin.
219+
181220
## Theme Customization
182221

183222
INTERSECT supports custom `.intersectstyle` themes. On first launch it creates default `dark.intersectstyle` and `light.intersectstyle` in the user theme directory.
@@ -194,9 +233,9 @@ Create a custom theme:
194233
2. Set a unique `name:` value (used in the UI theme list).
195234
3. Edit colors as 6-digit hex `RRGGBB`.
196235
4. Place the file in your user theme folder.
197-
5. Restart the plugin, then use the **UI** button in the header to select the theme.
236+
5. Restart the plugin, then use the **SET** button in the header to select the theme.
198237

199-
The **UI** button popup also controls interface scale (`0.5x` to `3.0x` in `0.25` steps).
238+
The **SET** button popup also controls interface scale (`0.5x` to `3.0x` in `0.25` steps).
200239

201240
## Build from Source
202241

src/PluginEditor.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,10 @@ bool IntersectEditor::keyPressed (const juce::KeyPress& key)
133133
if (mods.isCommandDown() || mods.isAltDown())
134134
return false;
135135

136-
// Esc - Close Auto Chop panel (only if open)
137-
if (code == juce::KeyPress::escapeKey && actionPanel.isAutoChopOpen())
136+
// Esc - Close Auto Chop panel if open
137+
if (code == juce::KeyPress::escapeKey)
138138
{
139-
actionPanel.toggleAutoChop();
140-
return true;
139+
if (actionPanel.isAutoChopOpen()) { actionPanel.toggleAutoChop(); return true; }
141140
}
142141

143142
// Shift shortcuts keep plain letter keys available for DAW keyboard MIDI.
@@ -387,6 +386,9 @@ void IntersectEditor::saveUserSettings (float scale, const juce::String& themeNa
387386
juce::String content;
388387
content << "uiScale: " << juce::String (scale, 2) << "\n";
389388
content << "theme: " << themeName << "\n";
389+
content << "nrpnEnabled: " << (processor.midiEditState.enabled.load (std::memory_order_relaxed) ? "true" : "false") << "\n";
390+
content << "nrpnChannel: " << processor.midiEditState.channel.load (std::memory_order_relaxed) << "\n";
391+
content << "nrpnBlockCc: " << (processor.midiEditState.consumeMidiEditCc.load (std::memory_order_relaxed) ? "true" : "false") << "\n";
390392
file.replaceWithText (content);
391393
}
392394

@@ -412,6 +414,21 @@ void IntersectEditor::loadUserSettings()
412414
{
413415
themeName = line.fromFirstOccurrenceOf (":", false, false).trim();
414416
}
417+
else if (line.startsWith ("nrpnEnabled:"))
418+
{
419+
auto val = line.fromFirstOccurrenceOf (":", false, false).trim();
420+
processor.midiEditState.enabled.store (val == "true", std::memory_order_relaxed);
421+
}
422+
else if (line.startsWith ("nrpnChannel:"))
423+
{
424+
int ch = line.fromFirstOccurrenceOf (":", false, false).trim().getIntValue();
425+
processor.midiEditState.channel.store (juce::jlimit (0, 16, ch), std::memory_order_relaxed);
426+
}
427+
else if (line.startsWith ("nrpnBlockCc:"))
428+
{
429+
auto val = line.fromFirstOccurrenceOf (":", false, false).trim();
430+
processor.midiEditState.consumeMidiEditCc.store (val == "true", std::memory_order_relaxed);
431+
}
415432
}
416433
}
417434

src/PluginEditor.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ class IntersectEditor : public juce::AudioProcessorEditor,
2222

2323
juce::StringArray getAvailableThemes();
2424
void applyTheme (const juce::String& themeName);
25+
void saveUserSettings (float scale, const juce::String& themeName);
2526

2627
private:
2728
void timerCallback() override;
2829
void ensureDefaultThemes();
29-
void saveUserSettings (float scale, const juce::String& themeName);
3030
void loadUserSettings();
3131

3232
IntersectProcessor& processor;

0 commit comments

Comments
 (0)