diff --git a/src/spectrogram.c b/src/spectrogram.c index 4c02685..824cd90 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -1123,27 +1123,50 @@ static void DrawLabels(Rectangle bounds) DrawTextScaled(label, x, bounds.y + bounds.height + 5, baseFontSize, textColor); } - // Frequency labels at 1kHz intervals with 200Hz ticks + // Frequency labels adapted to current zoom level float maxFreq = (float)app.signal.sampleRate / 2.0f; - int maxKhz = (int)(maxFreq / 1000.0f); + float freqMin = app.freqViewStart * maxFreq; + float freqMax = app.freqViewEnd * maxFreq; - for (int hz = 0; hz <= maxFreq; hz += 200) { - float t = hz / maxFreq; + // Choose tick spacing based on zoom range + float freqRange = freqMax - freqMin; + int tickSpacing; + if (freqRange < 20) tickSpacing = 5; + else if (freqRange < 50) tickSpacing = 10; + else if (freqRange < 200) tickSpacing = 50; + else if (freqRange < 1000) tickSpacing = 100; + else if (freqRange < 5000) tickSpacing = 200; + else if (freqRange < 20000) tickSpacing = 1000; + else if (freqRange < 50000) tickSpacing = 5000; + else tickSpacing = 10000; + + // Labels use next coarser spacing so they stay readable + int labelSpacing = tickSpacing; + if (labelSpacing <= 10) labelSpacing = 10; + else if (labelSpacing <= 50) labelSpacing = 50; + else if (labelSpacing <= 200) labelSpacing = 200; + else if (labelSpacing <= 1000) labelSpacing = 1000; + else if (labelSpacing <= 5000) labelSpacing = 5000; + else labelSpacing = 10000; + + // Round freqMin up to nearest tick spacing (smallest multiple >= freqMin) + int firstTick = ((int)(freqMin / tickSpacing)) * tickSpacing; + if (firstTick < freqMin) firstTick += tickSpacing; + for (int hz = firstTick; hz <= freqMax; hz += tickSpacing) { + float t = (hz - freqMin) / freqRange; float y = bounds.y + bounds.height - t * bounds.height; Color tickColor = (hz % 1000 == 0) ? GRAY : Fade(GRAY, 0.4f); DrawLineV((Vector2){ bounds.x - 5, y }, (Vector2){ bounds.x, y }, tickColor); } - for (int khz = 0; khz <= maxKhz; khz++) { - float freq = khz * 1000.0f; - if (freq > maxFreq) break; - float t = freq / maxFreq; + // Draw labels at the coarser spacing + for (int hz = firstTick; hz <= freqMax; hz += labelSpacing) { + float t = (hz - freqMin) / freqRange; float y = bounds.y + bounds.height - t * bounds.height; char label[32]; - if (khz == 0) sprintf(label, "0"); - else if (khz < 10) sprintf(label, "%dk", khz); - else sprintf(label, "%d", khz); - DrawTextScaled(label, bounds.x - 15, y - 5, baseFontSize, textColor); + if (hz < 10000) sprintf(label, "%.0fHz", (float)hz); + else sprintf(label, "%.0fkHz", (float)hz / 1000.0f); + DrawTextScaled(label, bounds.x - 70, y - 5, baseFontSize, textColor); } } @@ -1693,14 +1716,14 @@ int main(int argc, char* argv[]) mouseF = app.freqViewStart + mouseF * (app.freqViewEnd - app.freqViewStart); float freqWidth = app.freqViewEnd - app.freqViewStart; float newFreqWidth = freqWidth * zoomFactor; - if (newFreqWidth < 0.02f) newFreqWidth = 0.02f; - if (newFreqWidth > 1.0f) newFreqWidth = 1.0f; + if (newFreqWidth < 0.001f) newFreqWidth = 0.001f; float belowMouse = mouseF - app.freqViewStart; float aboveMouse = app.freqViewEnd - mouseF; app.freqViewStart = mouseF - belowMouse * (newFreqWidth / freqWidth); app.freqViewEnd = mouseF + aboveMouse * (newFreqWidth / freqWidth); - if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = newFreqWidth; } - if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = 1 - newFreqWidth; } + // Clamp to physical frequency limits [0, 1] — can't see beyond Nyquist or below 0 Hz + if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = fminf(app.freqViewEnd, 1.0f); } + if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = fmaxf(app.freqViewStart, 0.0f); } // Invalidate texture cache app.visibleTextureValid = false; @@ -1728,8 +1751,19 @@ int main(int argc, char* argv[]) if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - viewWidth; } app.freqViewStart = app.panStartFreqViewStart + dy * freqWidth; app.freqViewEnd = app.panStartFreqViewEnd + dy * freqWidth; - if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = freqWidth; } - if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = 1 - freqWidth; } + // Clamp to physical limits [0, 1] + if (app.freqViewStart < 0) { + float actualWidth = app.freqViewEnd - app.freqViewStart; + app.freqViewStart = 0; + app.freqViewEnd = fminf(actualWidth, 1.0f); + } + if (app.freqViewEnd > 1) { + float actualWidth = app.freqViewEnd - app.freqViewStart; + app.freqViewEnd = 1; + app.freqViewStart = fmaxf(1.0f - actualWidth, 0.0f); + } + if (app.freqViewStart < 0) app.freqViewStart = 0; + if (app.freqViewEnd > 1) app.freqViewEnd = 1; app.visibleTextureValid = false; } if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) app.isPanning = false; @@ -2182,8 +2216,16 @@ int main(int argc, char* argv[]) float freqWidth = app.freqViewEnd - app.freqViewStart; app.freqViewStart = dragStartFreqViewStart - dy; app.freqViewEnd = app.freqViewStart + freqWidth; - if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = freqWidth; } - if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = 1 - freqWidth; } + if (app.freqViewStart < 0) { + app.freqViewStart = 0; + app.freqViewEnd = fminf(freqWidth, 1.0f); + } + if (app.freqViewEnd > 1) { + app.freqViewEnd = 1; + app.freqViewStart = fmaxf(1.0f - freqWidth, 0.0f); + } + if (app.freqViewStart < 0) app.freqViewStart = 0; + if (app.freqViewEnd > 1) app.freqViewEnd = 1; app.visibleTextureValid = false; } if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { draggingH = false; draggingV = false; }