1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
/*
==============================================================================
JUCE demo code - use at your own risk!
==============================================================================
*/
class SpectrogramComponent : public AudioAppComponent,
private Timer
{
public:
SpectrogramComponent()
: forwardFFT (fftOrder, false),
spectrogramImage (Image::RGB, 512, 512, true),
fifoIndex (0),
nextFFTBlockReady (false)
{
setOpaque (true);
setAudioChannels (2, 0); // we want a couple of input channels but no outputs
startTimerHz (60);
setSize (700, 500);
}
~SpectrogramComponent()
{
shutdownAudio();
}
//==============================================================================
void prepareToPlay (int /*samplesPerBlockExpected*/, double /*newSampleRate*/) override
{
// (nothing to do here)
}
void releaseResources() override
{
// (nothing to do here)
}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
if (bufferToFill.buffer->getNumChannels() > 0)
{
const float* channelData = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
for (int i = 0; i < bufferToFill.numSamples; ++i)
pushNextSampleIntoFifo (channelData[i]);
}
}
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (Colours::black);
g.setOpacity (1.0f);
g.drawImage (spectrogramImage, getLocalBounds().toFloat());
}
void timerCallback() override
{
if (nextFFTBlockReady)
{
drawNextLineOfSpectrogram();
nextFFTBlockReady = false;
repaint();
}
}
void pushNextSampleIntoFifo (float sample) noexcept
{
// if the fifo contains enough data, set a flag to say
// that the next line should now be rendered..
if (fifoIndex == fftSize)
{
if (! nextFFTBlockReady)
{
zeromem (fftData, sizeof (fftData));
memcpy (fftData, fifo, sizeof (fifo));
nextFFTBlockReady = true;
}
fifoIndex = 0;
}
fifo[fifoIndex++] = sample;
}
void drawNextLineOfSpectrogram()
{
const int rightHandEdge = spectrogramImage.getWidth() - 1;
const int imageHeight = spectrogramImage.getHeight();
// first, shuffle our image leftwards by 1 pixel..
spectrogramImage.moveImageSection (0, 0, 1, 0, rightHandEdge, imageHeight);
// then render our FFT data..
forwardFFT.performFrequencyOnlyForwardTransform (fftData);
// find the range of values produced, so we can scale our rendering to
// show up the detail clearly
Range<float> maxLevel = FloatVectorOperations::findMinAndMax (fftData, fftSize / 2);
for (int y = 1; y < imageHeight; ++y)
{
const float skewedProportionY = 1.0f - std::exp (std::log (y / (float) imageHeight) * 0.2f);
const int fftDataIndex = jlimit (0, fftSize / 2, (int) (skewedProportionY * fftSize / 2));
const float level = jmap (fftData[fftDataIndex], 0.0f, maxLevel.getEnd(), 0.0f, 1.0f);
spectrogramImage.setPixelAt (rightHandEdge, y, Colour::fromHSV (level, 1.0f, level, 1.0f));
}
}
enum
{
fftOrder = 10,
fftSize = 1 << fftOrder
};
private:
FFT forwardFFT;
Image spectrogramImage;
float fifo [fftSize];
float fftData [2 * fftSize];
int fifoIndex;
bool nextFFTBlockReady;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SpectrogramComponent)
};
|