Sitar118 Claude Sonnet 4.6 commited on
Commit
6b15202
Β·
1 Parent(s): 225322d

feat: channel name dropdown in Editor (Sprint 6 Part 5)

Browse files

Two-row header: row 1 = existing controls, row 2 = channel ComboBox.
30 channel items grouped into Vocals/Drums/Bass/Keys/Synths/Strings/FX/Buses/Custom.
Custom... opens AlertWindow for free-text entry. Selection calls
processorRef.setChannelName() which updates registry + MCP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Source/Plugin/PluginBridgeEditor.h CHANGED
@@ -26,10 +26,11 @@ private:
26
  PluginBridgeProcessor& processorRef;
27
 
28
  // Top bar controls
29
- juce::Label titleLabel;
30
- juce::Label mcpStatusLabel;
31
- juce::Label pluginStatusLabel;
32
  juce::TextButton selectButton{"Select Plugin"};
 
33
 
34
  // In-process plugin editor (null for unsafe plugins)
35
  std::unique_ptr<juce::AudioProcessorEditor> pluginEditor;
@@ -43,10 +44,12 @@ private:
43
  bool showingError = false;
44
  bool audioOnlyMode = false;
45
 
46
- static constexpr int kTopBarHeight = 32;
47
- static constexpr int kAudioOnlyHeight = 80;
48
- static constexpr int kDefaultWidth = PluginPickerComponent::kWidth;
49
- static constexpr int kDefaultHeight = kTopBarHeight;
 
 
50
 
51
  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginBridgeEditor)
52
  };
 
26
  PluginBridgeProcessor& processorRef;
27
 
28
  // Top bar controls
29
+ juce::Label titleLabel;
30
+ juce::Label mcpStatusLabel;
31
+ juce::Label pluginStatusLabel;
32
  juce::TextButton selectButton{"Select Plugin"};
33
+ juce::ComboBox channelDropdown;
34
 
35
  // In-process plugin editor (null for unsafe plugins)
36
  std::unique_ptr<juce::AudioProcessorEditor> pluginEditor;
 
44
  bool showingError = false;
45
  bool audioOnlyMode = false;
46
 
47
+ static constexpr int kTopBarHeight = 32;
48
+ static constexpr int kChannelBarHeight = 26;
49
+ static constexpr int kHeaderHeight = kTopBarHeight + kChannelBarHeight;
50
+ static constexpr int kAudioOnlyHeight = 80;
51
+ static constexpr int kDefaultWidth = PluginPickerComponent::kWidth;
52
+ static constexpr int kDefaultHeight = kHeaderHeight;
53
 
54
  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginBridgeEditor)
55
  };
Source/Plugin/PluginBridgeEditor.mm CHANGED
@@ -50,6 +50,97 @@ PluginBridgeEditor::PluginBridgeEditor(PluginBridgeProcessor& p)
50
  selectButton.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff404040));
51
  addAndMakeVisible(selectButton);
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  // ── Audio-only label ──────────────────────────────────────────────────────
54
  audioOnlyLabel.setFont(juce::FontOptions(11.0f));
55
  audioOnlyLabel.setJustificationType(juce::Justification::centred);
@@ -142,13 +233,13 @@ void PluginBridgeEditor::paint(juce::Graphics& g)
142
  {
143
  g.fillAll(kBgDark);
144
 
145
- // Top bar
146
  g.setColour(kBgTopBar);
147
- g.fillRect(getLocalBounds().removeFromTop(kTopBarHeight));
148
 
149
- // Separator line under top bar
150
  g.setColour(juce::Colour(0xff333333));
151
- g.fillRect(0, kTopBarHeight - 1, getWidth(), 1);
152
 
153
  // Empty state hint (only when no picker, no plugin, no error)
154
  if (!activePicker && !pluginEditor && !audioOnlyMode
@@ -157,22 +248,26 @@ void PluginBridgeEditor::paint(juce::Graphics& g)
157
  g.setColour(juce::Colour(0xff444444));
158
  g.setFont(juce::FontOptions(12.0f));
159
  g.drawText("Click Select Plugin to browse your library",
160
- getLocalBounds().withTrimmedTop(kTopBarHeight),
161
  juce::Justification::centred);
162
  }
163
  }
164
 
165
  void PluginBridgeEditor::resized()
166
  {
167
- auto area = getLocalBounds();
168
- auto topBar = area.removeFromTop(kTopBarHeight).reduced(6, 4);
169
 
 
 
170
  titleLabel.setBounds(topBar.removeFromLeft(80));
171
  selectButton.setBounds(topBar.removeFromLeft(110).reduced(0, 2));
172
  topBar.removeFromLeft(6);
173
  pluginStatusLabel.setBounds(topBar.removeFromLeft(topBar.getWidth() - 64));
174
  mcpStatusLabel.setBounds(topBar);
175
 
 
 
 
176
  // Content area: picker > plugin editor > audio-only (priority order)
177
  if (activePicker)
178
  activePicker->setBounds(area);
@@ -299,7 +394,7 @@ void PluginBridgeEditor::showPluginMenu()
299
 
300
  // Resize editor to show the picker
301
  int newW = std::max(getWidth(), PluginPickerComponent::kWidth);
302
- int newH = kTopBarHeight + PluginPickerComponent::kHeight;
303
  setSize(newW, newH);
304
 
305
  PBLOG("Editor", "showPluginMenu: picker opened");
@@ -318,12 +413,12 @@ void PluginBridgeEditor::hidePicker()
318
  {
319
  pluginEditor->setVisible(true);
320
  setSize(std::max(pluginEditor->getWidth(), kDefaultWidth),
321
- pluginEditor->getHeight() + kTopBarHeight);
322
  }
323
  else if (audioOnlyMode)
324
  {
325
  audioOnlyLabel.setVisible(true);
326
- setSize(kDefaultWidth, kTopBarHeight + kAudioOnlyHeight);
327
  }
328
  else
329
  {
@@ -358,7 +453,7 @@ void PluginBridgeEditor::attachPluginEditor()
358
 
359
  addAndMakeVisible(pluginEditor.get());
360
  int w = std::max(pluginEditor->getWidth(), kDefaultWidth);
361
- int h = pluginEditor->getHeight() + kTopBarHeight;
362
  PBLOG("Editor", "attachPluginEditor: SUCCESS β€” size " + juce::String(w) + "x" + juce::String(h));
363
  setSize(w, h);
364
  }
@@ -387,7 +482,7 @@ void PluginBridgeEditor::showAudioOnlyUI()
387
  "Audio processing active. Use MCP or automation for parameters.",
388
  juce::dontSendNotification);
389
  audioOnlyLabel.setVisible(true);
390
- setSize(kDefaultWidth, kTopBarHeight + kAudioOnlyHeight);
391
  }
392
 
393
  void PluginBridgeEditor::hideAudioOnlyUI()
 
50
  selectButton.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff404040));
51
  addAndMakeVisible(selectButton);
52
 
53
+ // ── Channel name dropdown ─────────────────────────────────────────────────
54
+ channelDropdown.addSectionHeading("── Vocals ──");
55
+ channelDropdown.addItem("Lead Vocal", 1);
56
+ channelDropdown.addItem("Backing Vocal", 2);
57
+ channelDropdown.addItem("Adlibs", 3);
58
+ channelDropdown.addSeparator();
59
+ channelDropdown.addSectionHeading("── Drums ──");
60
+ channelDropdown.addItem("Kick", 4);
61
+ channelDropdown.addItem("Snare", 5);
62
+ channelDropdown.addItem("Hi-Hat", 6);
63
+ channelDropdown.addItem("Overheads", 7);
64
+ channelDropdown.addItem("Percussion", 8);
65
+ channelDropdown.addSeparator();
66
+ channelDropdown.addSectionHeading("── Bass ──");
67
+ channelDropdown.addItem("Bass / 808", 9);
68
+ channelDropdown.addItem("Bass Guitar", 10);
69
+ channelDropdown.addItem("Sub Bass", 11);
70
+ channelDropdown.addItem("Synth Bass", 12);
71
+ channelDropdown.addSeparator();
72
+ channelDropdown.addSectionHeading("── Keys & Guitar ──");
73
+ channelDropdown.addItem("Piano / Keys", 13);
74
+ channelDropdown.addItem("Acoustic Guitar",14);
75
+ channelDropdown.addItem("Electric Guitar",15);
76
+ channelDropdown.addSeparator();
77
+ channelDropdown.addSectionHeading("── Synths ──");
78
+ channelDropdown.addItem("Synth Lead", 16);
79
+ channelDropdown.addItem("Synth Pad / Pluck", 17);
80
+ channelDropdown.addSeparator();
81
+ channelDropdown.addSectionHeading("── Strings & Brass ──");
82
+ channelDropdown.addItem("Strings", 18);
83
+ channelDropdown.addItem("Brass / Woodwind", 19);
84
+ channelDropdown.addItem("Orchestral", 20);
85
+ channelDropdown.addSeparator();
86
+ channelDropdown.addSectionHeading("── FX ──");
87
+ channelDropdown.addItem("Reverb / Delay", 21);
88
+ channelDropdown.addItem("Foley / Ambient",22);
89
+ channelDropdown.addSeparator();
90
+ channelDropdown.addSectionHeading("── Buses ──");
91
+ channelDropdown.addItem("Vocal Bus", 23);
92
+ channelDropdown.addItem("Drum Bus", 24);
93
+ channelDropdown.addItem("Guitar Bus", 25);
94
+ channelDropdown.addItem("Synth Bus", 26);
95
+ channelDropdown.addItem("Music Bus", 27);
96
+ channelDropdown.addItem("Instrument Bus", 28);
97
+ channelDropdown.addItem("Master Bus / Mix Bus", 29);
98
+ channelDropdown.addSeparator();
99
+ channelDropdown.addSectionHeading("── Custom ──");
100
+ channelDropdown.addItem("Custom...", 30);
101
+
102
+ channelDropdown.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2d2d2d));
103
+ channelDropdown.setColour(juce::ComboBox::textColourId, kTextPrimary);
104
+ channelDropdown.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff404040));
105
+ channelDropdown.setColour(juce::ComboBox::arrowColourId, kTextSecondary);
106
+ channelDropdown.setTextWhenNothingSelected("Select channel...");
107
+
108
+ // Restore saved channel name
109
+ channelDropdown.setText(processorRef.getChannelName(), juce::dontSendNotification);
110
+
111
+ channelDropdown.onChange = [this]()
112
+ {
113
+ auto text = channelDropdown.getText();
114
+ if (text == "Custom...")
115
+ {
116
+ auto* aw = new juce::AlertWindow("Channel Name",
117
+ "Enter a name for this channel:",
118
+ juce::MessageBoxIconType::NoIcon);
119
+ aw->addTextEditor("name", processorRef.getChannelName());
120
+ aw->addButton("OK", 1);
121
+ aw->addButton("Cancel", 0);
122
+ aw->enterModalState(true,
123
+ juce::ModalCallbackFunction::create([this, aw](int result)
124
+ {
125
+ if (result == 1)
126
+ {
127
+ auto name = aw->getTextEditorContents("name");
128
+ if (name.isNotEmpty())
129
+ {
130
+ channelDropdown.setText(name, juce::dontSendNotification);
131
+ processorRef.setChannelName(name);
132
+ }
133
+ }
134
+ delete aw;
135
+ }), true);
136
+ }
137
+ else if (text.isNotEmpty())
138
+ {
139
+ processorRef.setChannelName(text);
140
+ }
141
+ };
142
+ addAndMakeVisible(channelDropdown);
143
+
144
  // ── Audio-only label ──────────────────────────────────────────────────────
145
  audioOnlyLabel.setFont(juce::FontOptions(11.0f));
146
  audioOnlyLabel.setJustificationType(juce::Justification::centred);
 
233
  {
234
  g.fillAll(kBgDark);
235
 
236
+ // Header (top bar + channel bar)
237
  g.setColour(kBgTopBar);
238
+ g.fillRect(getLocalBounds().removeFromTop(kHeaderHeight));
239
 
240
+ // Separator line under header
241
  g.setColour(juce::Colour(0xff333333));
242
+ g.fillRect(0, kHeaderHeight - 1, getWidth(), 1);
243
 
244
  // Empty state hint (only when no picker, no plugin, no error)
245
  if (!activePicker && !pluginEditor && !audioOnlyMode
 
248
  g.setColour(juce::Colour(0xff444444));
249
  g.setFont(juce::FontOptions(12.0f));
250
  g.drawText("Click Select Plugin to browse your library",
251
+ getLocalBounds().withTrimmedTop(kHeaderHeight),
252
  juce::Justification::centred);
253
  }
254
  }
255
 
256
  void PluginBridgeEditor::resized()
257
  {
258
+ auto area = getLocalBounds();
 
259
 
260
+ // Row 1: title + select button + status + MCP port
261
+ auto topBar = area.removeFromTop(kTopBarHeight).reduced(6, 4);
262
  titleLabel.setBounds(topBar.removeFromLeft(80));
263
  selectButton.setBounds(topBar.removeFromLeft(110).reduced(0, 2));
264
  topBar.removeFromLeft(6);
265
  pluginStatusLabel.setBounds(topBar.removeFromLeft(topBar.getWidth() - 64));
266
  mcpStatusLabel.setBounds(topBar);
267
 
268
+ // Row 2: channel name dropdown (full width)
269
+ channelDropdown.setBounds(area.removeFromTop(kChannelBarHeight).reduced(6, 2));
270
+
271
  // Content area: picker > plugin editor > audio-only (priority order)
272
  if (activePicker)
273
  activePicker->setBounds(area);
 
394
 
395
  // Resize editor to show the picker
396
  int newW = std::max(getWidth(), PluginPickerComponent::kWidth);
397
+ int newH = kHeaderHeight + PluginPickerComponent::kHeight;
398
  setSize(newW, newH);
399
 
400
  PBLOG("Editor", "showPluginMenu: picker opened");
 
413
  {
414
  pluginEditor->setVisible(true);
415
  setSize(std::max(pluginEditor->getWidth(), kDefaultWidth),
416
+ pluginEditor->getHeight() + kHeaderHeight);
417
  }
418
  else if (audioOnlyMode)
419
  {
420
  audioOnlyLabel.setVisible(true);
421
+ setSize(kDefaultWidth, kHeaderHeight + kAudioOnlyHeight);
422
  }
423
  else
424
  {
 
453
 
454
  addAndMakeVisible(pluginEditor.get());
455
  int w = std::max(pluginEditor->getWidth(), kDefaultWidth);
456
+ int h = pluginEditor->getHeight() + kHeaderHeight;
457
  PBLOG("Editor", "attachPluginEditor: SUCCESS β€” size " + juce::String(w) + "x" + juce::String(h));
458
  setSize(w, h);
459
  }
 
482
  "Audio processing active. Use MCP or automation for parameters.",
483
  juce::dontSendNotification);
484
  audioOnlyLabel.setVisible(true);
485
+ setSize(kDefaultWidth, kHeaderHeight + kAudioOnlyHeight);
486
  }
487
 
488
  void PluginBridgeEditor::hideAudioOnlyUI()