@@ -30,6 +30,22 @@ SpectrogramAudioProcessorEditor::SpectrogramAudioProcessorEditor(SpectrogramAudi
3030 // Set sample rate
3131 spectrogram.setSampleRate (audioProcessor.getSampleRate ());
3232
33+ // Fixed display of fold/expand button (always visible)
34+ addAndMakeVisible (toggleUiButton);
35+ toggleUiButton.setTooltip (" Show/Hide control panels" );
36+ toggleUiButton.setAlwaysOnTop (true );
37+ toggleUiButton.onClick = [this ]()
38+ {
39+ controlsVisible = !controlsVisible;
40+ updateControlsVisibility ();
41+ // switch arrow
42+ toggleUiButton.setButtonText (controlsVisible ? HideMenuText : ShowMenuText);
43+ resized ();
44+ repaint ();
45+ };
46+ // init visibility
47+ updateControlsVisibility ();
48+
3349 // Add and configure freeze button
3450 addAndMakeVisible (freezeButton);
3551 freezeButton.setTooltip (" Freeze or resume spectrogram scrolling" );
@@ -40,6 +56,41 @@ SpectrogramAudioProcessorEditor::SpectrogramAudioProcessorEditor(SpectrogramAudi
4056 spectrogram.setFrozen (isFrozen);
4157 };
4258
59+ // Add and configure FFT size dropdown
60+ addAndMakeVisible (fftSizeBox);
61+ fftSizeBox.setTooltip (" Select FFT window size. Larger = better frequency, worse time resolution." );
62+ fftSizeBox.addItem (" 512" , 9 ); // 2^9
63+ fftSizeBox.addItem (" 1024" , 10 ); // 2^10
64+ fftSizeBox.addItem (" 2048" , 11 ); // 2^11
65+ fftSizeBox.addItem (" 4096" , 12 ); // 2^12
66+ fftSizeBox.addItem (" 8192" , 13 ); // 2^13
67+
68+ fftSizeBox.setSelectedId (11 ); // default: 2048
69+ fftSizeBox.onChange = [this ]()
70+ {
71+ const int newOrder = fftSizeBox.getSelectedId ();
72+ spectrogram.setFFTOrder (newOrder);
73+ updateLegendImage ();
74+ repaint ();
75+ };
76+
77+ // Add and configure scroll speed dropdown
78+ addAndMakeVisible (scrollSpeedBox);
79+ scrollSpeedBox.setTooltip (
80+ " Scroll speed (controls overlap)"
81+ );
82+ scrollSpeedBox.addItem (" x1" , 1 ); // overlap = 1
83+ scrollSpeedBox.addItem (" x2" , 2 ); // overlap = 2
84+ scrollSpeedBox.addItem (" x4" , 4 ); // overlap = 4
85+ scrollSpeedBox.addItem (" x8" , 8 ); // overlap = 8
86+
87+ scrollSpeedBox.setSelectedId (2 ); // default: overlap = 2
88+ scrollSpeedBox.onChange = [this ]()
89+ {
90+ const int ov = scrollSpeedBox.getSelectedId ();
91+ spectrogram.setOverlap (ov);
92+ };
93+
4394 // Add and configure colour scheme combo box
4495 addAndMakeVisible (colourSchemeBox);
4596 colourSchemeBox.setTooltip (" Select the color map used for the spectrogram display." );
@@ -65,12 +116,14 @@ SpectrogramAudioProcessorEditor::SpectrogramAudioProcessorEditor(SpectrogramAudi
65116 spectrogramModeBox.setTooltip (
66117 " Select the type of spectrogram to display.\n "
67118 " - Linear: Standard STFT spectrogram with linear or log frequency axis.\n "
119+ " - Linear+: Enhanced STFT spectrogram after time-frequency reassignment with linear or log frequency axis.\n "
68120 " - Mel: Mel-scaled spectrogram that spaces frequencies according to nonlinear human pitch perception.\n "
69121 " - MFCC: Mel-frequency cepstral coefficient, representing timbral texture. Typically used in audio classification and speech recognition.\n "
70122 " - Spectral Centroid: STFT spectrogram with added curves showing where the energy is centered and how widely it is spread across frequencies.\n "
71123 " - Chroma: Chromagram showing the energy distribution across the 12 pitch classes (C to B), regardless of octave. Useful for analyzing harmonic content and key."
72124 );
73125 spectrogramModeBox.addItem (" Linear" , static_cast <int >(SpectrogramComponent::SpectrogramMode::Linear));
126+ spectrogramModeBox.addItem (" Linear+" , static_cast <int >(SpectrogramComponent::SpectrogramMode::LinearPlus));
74127 spectrogramModeBox.addItem (" Mel" , static_cast <int >(SpectrogramComponent::SpectrogramMode::Mel));
75128 spectrogramModeBox.addItem (" MFCC" , static_cast <int >(SpectrogramComponent::SpectrogramMode::MFCC));
76129 spectrogramModeBox.addItem (" Spectral Centroid" , static_cast <int >(SpectrogramComponent::SpectrogramMode::LinearWithCentroid));
@@ -159,30 +212,34 @@ void SpectrogramAudioProcessorEditor::paint(juce::Graphics& g)
159212
160213 // Legend aligned to topBar (same vertical height), placed at top-right corner
161214 const int topBarHeight = 30 ;
162- const int margin = 40 ;
215+ const int baseMargin = 40 ;
216+ const int rightReserve = baseMargin + toggleW + gap;
163217
164- const int legendX = getWidth () - legendImage.getWidth () - margin;
165- const int legendY = (topBarHeight - legendImage.getHeight ()) / 2 ; // vertical center inside topBar
218+ if (controlsVisible)
219+ {
220+ const int legendX = getWidth () - legendImage.getWidth () - rightReserve;
221+ const int legendY = (topBarHeight - legendImage.getHeight ()) / 2 ; // vertical center inside topBar
166222
167- // Draw legend color bar
168- g.drawImage (legendImage, legendX, legendY, legendImage.getWidth (), legendImage.getHeight (),
169- 0 , 0 , legendImage.getWidth (), legendImage.getHeight ());
223+ // Draw legend color bar
224+ g.drawImage (legendImage, legendX, legendY, legendImage.getWidth (), legendImage.getHeight (),
225+ 0 , 0 , legendImage.getWidth (), legendImage.getHeight ());
170226
171- // dB labels
172- g.setColour (juce::Colours::white);
173- g.setFont (12 .0f );
227+ // dB labels
228+ g.setColour (juce::Colours::white);
229+ g.setFont (12 .0f );
174230
175- if (spectrogram.getCurrentMode () == SpectrogramComponent::SpectrogramMode::MFCC ||
176- spectrogram.getCurrentMode () == SpectrogramComponent::SpectrogramMode::Chroma)
177- {
178- // normalized legend label for MFCC and Chromagram [0, 1]
179- g.drawText (" 0.0" , legendX - 50 , legendY, 45 , legendImage.getHeight (), juce::Justification::right);
180- g.drawText (" 1.0" , legendX + legendImage.getWidth () + 5 , legendY, 40 , legendImage.getHeight (), juce::Justification::left);
181- }
182- else
183- {
184- g.drawText (juce::String ((int )spectrogram.getFloorDb ()) + " dB" , legendX - 50 , legendY, 45 , legendImage.getHeight (), juce::Justification::right);
185- g.drawText (" 0 dB" , legendX + legendImage.getWidth () + 5 , legendY, 40 , legendImage.getHeight (), juce::Justification::left);
231+ if (spectrogram.getCurrentMode () == SpectrogramComponent::SpectrogramMode::MFCC ||
232+ spectrogram.getCurrentMode () == SpectrogramComponent::SpectrogramMode::Chroma)
233+ {
234+ // normalized legend label for MFCC and Chromagram [0, 1]
235+ g.drawText (" 0.0" , legendX - 50 , legendY, 45 , legendImage.getHeight (), juce::Justification::right);
236+ g.drawText (" 1.0" , legendX + legendImage.getWidth () + 5 , legendY, 40 , legendImage.getHeight (), juce::Justification::left);
237+ }
238+ else
239+ {
240+ g.drawText (juce::String ((int )spectrogram.getFloorDb ()) + " dB" , legendX - 50 , legendY, 45 , legendImage.getHeight (), juce::Justification::right);
241+ g.drawText (" 0 dB" , legendX + legendImage.getWidth () + 5 , legendY, 40 , legendImage.getHeight (), juce::Justification::left);
242+ }
186243 }
187244}
188245
@@ -203,25 +260,55 @@ void SpectrogramAudioProcessorEditor::updateLegendImage()
203260 }
204261}
205262
263+ void SpectrogramAudioProcessorEditor::updateControlsVisibility ()
264+ {
265+ fftSizeBox.setVisible (controlsVisible);
266+ scrollSpeedBox.setVisible (controlsVisible);
267+ colourSchemeBox.setVisible (controlsVisible);
268+ spectrogramModeBox.setVisible (controlsVisible);
269+ logScaleBox.setVisible (controlsVisible);
270+ floorDbSlider.setVisible (controlsVisible);
271+ normFactorSlider.setVisible (controlsVisible);
272+ freezeButton.setVisible (controlsVisible);
273+ }
274+
206275void SpectrogramAudioProcessorEditor::resized ()
207276{
208277 auto area = getLocalBounds ();
209- // top row: freeze button & legend bar
210- auto topRow = area.removeFromTop (30 );
211- freezeButton.setBounds (topRow.removeFromLeft (100 ).reduced (5 ));
278+
279+ // fixed small button for menu visibility
280+ const int baseMargin = 6 ;
281+ toggleUiButton.setBounds (getWidth () - baseMargin - toggleW, baseMargin / 2 , toggleW, toggleW);
282+
283+ const int rowH = controlsVisible ? kRowHeight : 0 ;
284+
285+ // top row: freeze button & FFT settings & legend bar
286+ auto topRow = area.removeFromTop (rowH);
287+ if (controlsVisible)
288+ {
289+ // freeze button
290+ freezeButton.setBounds (topRow.removeFromLeft (100 ).reduced (5 ));
291+ // FFT size dropdown
292+ fftSizeBox.setBounds (topRow.removeFromLeft (100 ).reduced (5 ));
293+ // scroll speed (overlap)
294+ scrollSpeedBox.setBounds (topRow.removeFromLeft (100 ).reduced (5 ));
295+ }
212296
213297 // second row: dropdown menu etc.
214- auto secondRow = area.removeFromTop (30 );
215- // colour scheme
216- colourSchemeBox.setBounds (secondRow.removeFromLeft (110 ).reduced (5 ));
217- // spectrogram mode
218- spectrogramModeBox.setBounds (secondRow.removeFromLeft (110 ).reduced (5 ));
219- // y axis type: log or linear (for linear STFT spectrogram)
220- logScaleBox.setBounds (secondRow.removeFromLeft (110 ).reduced (5 ));
221- // slider floor value colour scheme
222- floorDbSlider.setBounds (secondRow.removeFromLeft (200 ).reduced (5 ));
223- // slider norm factor
224- normFactorSlider.setBounds (secondRow.removeFromLeft (200 ).reduced (5 ));
298+ auto secondRow = area.removeFromTop (rowH);
299+ if (controlsVisible)
300+ {
301+ // colour scheme
302+ colourSchemeBox.setBounds (secondRow.removeFromLeft (100 ).reduced (5 ));
303+ // spectrogram mode
304+ spectrogramModeBox.setBounds (secondRow.removeFromLeft (100 ).reduced (5 ));
305+ // y axis type: log or linear (for linear STFT spectrogram)
306+ logScaleBox.setBounds (secondRow.removeFromLeft (100 ).reduced (5 ));
307+ // slider floor value colour scheme
308+ floorDbSlider.setBounds (secondRow.removeFromLeft (200 ).reduced (5 ));
309+ // slider norm factor
310+ normFactorSlider.setBounds (secondRow.removeFromLeft (200 ).reduced (5 ));
311+ }
225312
226313 // rest: spectrogram
227314 spectrogram.setBounds (area);
0 commit comments