The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
4.6KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. class SpectrogramComponent : public AudioAppComponent,
  20. private Timer
  21. {
  22. public:
  23. SpectrogramComponent()
  24. : forwardFFT (fftOrder),
  25. spectrogramImage (Image::RGB, 512, 512, true),
  26. fifoIndex (0),
  27. nextFFTBlockReady (false)
  28. {
  29. setOpaque (true);
  30. setAudioChannels (2, 0); // we want a couple of input channels but no outputs
  31. startTimerHz (60);
  32. setSize (700, 500);
  33. }
  34. ~SpectrogramComponent()
  35. {
  36. shutdownAudio();
  37. }
  38. //==============================================================================
  39. void prepareToPlay (int /*samplesPerBlockExpected*/, double /*newSampleRate*/) override
  40. {
  41. // (nothing to do here)
  42. }
  43. void releaseResources() override
  44. {
  45. // (nothing to do here)
  46. }
  47. void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
  48. {
  49. if (bufferToFill.buffer->getNumChannels() > 0)
  50. {
  51. const float* channelData = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
  52. for (int i = 0; i < bufferToFill.numSamples; ++i)
  53. pushNextSampleIntoFifo (channelData[i]);
  54. }
  55. }
  56. //==============================================================================
  57. void paint (Graphics& g) override
  58. {
  59. g.fillAll (Colours::black);
  60. g.setOpacity (1.0f);
  61. g.drawImage (spectrogramImage, getLocalBounds().toFloat());
  62. }
  63. void timerCallback() override
  64. {
  65. if (nextFFTBlockReady)
  66. {
  67. drawNextLineOfSpectrogram();
  68. nextFFTBlockReady = false;
  69. repaint();
  70. }
  71. }
  72. void pushNextSampleIntoFifo (float sample) noexcept
  73. {
  74. // if the fifo contains enough data, set a flag to say
  75. // that the next line should now be rendered..
  76. if (fifoIndex == fftSize)
  77. {
  78. if (! nextFFTBlockReady)
  79. {
  80. zeromem (fftData, sizeof (fftData));
  81. memcpy (fftData, fifo, sizeof (fifo));
  82. nextFFTBlockReady = true;
  83. }
  84. fifoIndex = 0;
  85. }
  86. fifo[fifoIndex++] = sample;
  87. }
  88. void drawNextLineOfSpectrogram()
  89. {
  90. const int rightHandEdge = spectrogramImage.getWidth() - 1;
  91. const int imageHeight = spectrogramImage.getHeight();
  92. // first, shuffle our image leftwards by 1 pixel..
  93. spectrogramImage.moveImageSection (0, 0, 1, 0, rightHandEdge, imageHeight);
  94. // then render our FFT data..
  95. forwardFFT.performFrequencyOnlyForwardTransform (fftData);
  96. // find the range of values produced, so we can scale our rendering to
  97. // show up the detail clearly
  98. Range<float> maxLevel = FloatVectorOperations::findMinAndMax (fftData, fftSize / 2);
  99. for (int y = 1; y < imageHeight; ++y)
  100. {
  101. const float skewedProportionY = 1.0f - std::exp (std::log (y / (float) imageHeight) * 0.2f);
  102. const int fftDataIndex = jlimit (0, fftSize / 2, (int) (skewedProportionY * fftSize / 2));
  103. const float level = jmap (fftData[fftDataIndex], 0.0f, jmax (maxLevel.getEnd(), 1e-5f), 0.0f, 1.0f);
  104. spectrogramImage.setPixelAt (rightHandEdge, y, Colour::fromHSV (level, 1.0f, level, 1.0f));
  105. }
  106. }
  107. enum
  108. {
  109. fftOrder = 10,
  110. fftSize = 1 << fftOrder
  111. };
  112. private:
  113. dsp::FFT forwardFFT;
  114. Image spectrogramImage;
  115. float fifo [fftSize];
  116. float fftData [2 * fftSize];
  117. int fifoIndex;
  118. bool nextFFTBlockReady;
  119. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SpectrogramComponent)
  120. };