/*
==============================================================================
This file is part of the JUCE tutorials.
Copyright (c) 2017 - ROLI Ltd.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: ProcessingAudioInputTutorial
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Performs processing on an input signal.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_processors, juce_audio_utils, juce_core,
juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2017, linux_make
type: Component
mainClass: MainContentComponent
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************
Jonathan Feldman, May 2019
This version has the basical spectral drive algorithm working.
The incoming guitar gain is very low
The spectral components are being boosted but there is no distortion.
*/
#pragma once
#include
#include
using namespace std;
//==============================================================================
class MainContentComponent : public AudioAppComponent, private Timer
{
public:
//==============================================================================
MainContentComponent() : spectrogramFFT(fftOrder),
algorithmFFT(fftOrder),
spectrogramImage (Image::RGB, 512, 512, true)
{
//
// set parameter ranges for sliders
//
inputGainSlider.setRange (0, 10, 0.1);
ffSlider.setRange (0.0, 1.0, 0.1);
spreadSlider.setRange(1, 25, 0.5);
driveSlider.setRange (0, 250, 0.5);
//
// set up the sliders for display
//
inputGainSlider.setTextBoxStyle (Slider::TextBoxRight, false, 100, 20);
inputGainLabel.setText ("Input Gain", dontSendNotification);
ffSlider.setTextBoxStyle (Slider::TextBoxRight, false, 100, 20);
ffLabel.setText ("Floor Fraction", dontSendNotification);
spreadSlider.setTextBoxStyle (Slider::TextBoxRight, false, 100, 20);
spreadLabel.setText ("Spread", dontSendNotification);
driveSlider.setTextBoxStyle (Slider::TextBoxRight, false, 100, 20);
driveLabel.setText ("Drive", dontSendNotification);
//gainLabel.setText ("Max Input Gain", dontSendNotification);
//theGainLabel.setText ("Init", dontSendNotification);
//magnitudeLabel.setText ("Max FFT Magnitude", dontSendNotification);
//theMagnitudeLabel.setText ("Init", dontSendNotification);
addAndMakeVisible (inputGainSlider);
addAndMakeVisible (inputGainLabel);
addAndMakeVisible (ffSlider);
addAndMakeVisible (ffLabel);
addAndMakeVisible (spreadSlider);
addAndMakeVisible (spreadLabel);
addAndMakeVisible (driveSlider);
addAndMakeVisible (driveLabel);
//addAndMakeVisible (gainLabel);
//addAndMakeVisible (theGainLabel);
//addAndMakeVisible (magnitudeLabel);
//addAndMakeVisible (theMagnitudeLabel);
setSize (600, 100);
//
// set for mono input, mono output
//
// the guitar is coming in the left channel and going out the headphones
// which are connected to the left channel
//
// we will worry about stereo later
//
setAudioChannels (1, 1);
//
// set Spectrogram components
//
setOpaque (true);
startTimerHz (60);
setSize (700, 500);
}
~MainContentComponent()
{
shutdownAudio();
}
void prepareToPlay (int, double) override {}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
auto* device = deviceManager.getCurrentAudioDevice();
auto activeInputChannels = device->getActiveInputChannels();
auto activeOutputChannels = device->getActiveOutputChannels();
auto maxInputChannels = activeInputChannels .getHighestBit() + 1;
auto maxOutputChannels = activeOutputChannels.getHighestBit() + 1;
auto inputGainLevel = (float) inputGainSlider.getValue();
auto ffLevel = (float) ffSlider.getValue();
auto spreadLevel = (float) spreadSlider.getValue();
auto driveLevel = (float) driveSlider.getValue();
for (auto channel = 0; channel < maxOutputChannels; ++channel)
{
if ((! activeOutputChannels[channel]) || maxInputChannels == 0)
{
bufferToFill.buffer->clear (channel, bufferToFill.startSample, bufferToFill.numSamples);
}
else
{
auto actualInputChannel = channel % maxInputChannels;
if (! activeInputChannels[channel])
{
bufferToFill.buffer->clear (channel, bufferToFill.startSample, bufferToFill.numSamples);
}
else //
{
auto* inBuffer = bufferToFill.buffer->getReadPointer (actualInputChannel, bufferToFill.startSample);
// we send the samples to the fifo to reverse the order of the samples
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
sendBufferedDataToFifoForSpectrogram(inBuffer[sample]);
}
//
// reverse the order of the samples in the sample buffer to send to FFT
//
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
queuedSampleBuffer[sample] = inBuffer[fftSize-1-sample];
}
//
// perform the forward FFT
//
algorithmFFT.perform(queuedSampleBuffer, forwardFFTData, false);
//
// compute magnitude and phase information
//
for (int i = 0; i < 2*fftSize; i+=2)
{
magnitude[i] = abs(forwardFFTData[i]);
if (real(forwardFFTData[i]) > 0)
{
phase[i] = atan(imag(forwardFFTData[i]) / real(forwardFFTData[i]));
}
else
{
phase[i] = M_PI + atan(imag(forwardFFTData[i]) / real(forwardFFTData[i]));
}
}
//
// The maximum amplitude of the incoming guitar signal is about 0.01, very low gain
//
//
// copy slider levels onto algorithm parameters
// this is redundant code
//
floorFraction = ffLevel;
spread = spreadLevel;
drive = driveLevel;
for (int i = 0; i < 2*fftSize; i++)
{
if (magnitude[i] > floorFraction) // 50 is some arbitrary FFT magnitude maximum
{
magnitude[i] = magnitude[i] * spread + drive;
}
}
//
// reconstruct the Fourier data for the inverse transform
//
zeromem(inverseFFTData, sizeof(inverseFFTData));
for (int i = 0; i < 2*fftSize; i++)
{
float newReal = magnitude[i] * cos(phase[i]);
float newComplex = magnitude[i] * sin(phase[i]);
complex
fxFFTData[i] = c;
}
algorithmFFT.perform(fxFFTData, inverseFFTData, true);
//
// we reverse the order of the samples coming out of the inverse fft
// to send to the output buffer
//
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
outputSampleBuffer[sample] = real(inverseFFTData[fftSize-1-sample]);
}
auto* outBuffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
outBuffer[sample] = outputSampleBuffer[sample] * inputGainLevel;
}
}
}
}
}
void sendBufferedDataToFifoForSpectrogram(float sample) noexcept
{
if (fifoIndex == fftSize)
{
if (!nextFFTBlockReady)
{
zeromem(forwardFFTData, sizeof(fftData));
memcpy(forwardFFTData, fifo, sizeof(fifo));
nextFFTBlockReady = true;
}
fifoIndex = 0;
}
fifo[fifoIndex++] = sample;
}
void releaseResources() override {}
void resized() override
{
inputGainLabel .setBounds (10, 10, 90, 20);
inputGainSlider.setBounds (100, 10, getWidth() - 110, 20);
ffLabel .setBounds (10, 40, 90, 20);
ffSlider.setBounds (100, 40, getWidth() - 110, 20);
spreadLabel .setBounds (10, 70, 90, 20);
spreadSlider.setBounds (100, 70, getWidth() - 110, 20);
driveLabel .setBounds (10, 100, 90, 20);
driveSlider.setBounds (100, 100, getWidth() - 110, 20);
gainLabel.setBounds (10, 130, 90, 20);
theGainLabel.setBounds (100, 130, 90, 20);
magnitudeLabel.setBounds (10, 160, 90, 20);
theMagnitudeLabel.setBounds (100, 160, 90, 20);
//
// this code should probably be called from the paint() function
// in order to use it here we need to resize the window
//
//auto gainString = String::formatted ("%f", inputGainTest);
//theGainLabel.setText (gainString, dontSendNotification);
//auto magnitudeString = String::formatted ("%f", fftMagTest);
//theMagnitudeLabel.setText (magnitudeString, dontSendNotification);
}
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 drawNextLineOfSpectrogram()
{
auto rightHandEdge = spectrogramImage.getWidth() - 1;
auto imageHeight = spectrogramImage.getHeight();
// first, shuffle our image leftwards by 1 pixel..
spectrogramImage.moveImageSection (0, 0, 1, 0, rightHandEdge, imageHeight);
//
// performFrequencyOnlyForwardTransform works here with fftData, the spectrogram is clean
//
// spectrogramFFT.performFrequencyOnlyForwardTransform(fftData);
//
//
// now try using a complex valued FFT
//
spectrogramFFT.perform(fifo /*input*/, fftData /*output*/,false);
for (int i = 0; i < fftSize; i++)
{
magnitude[i] = abs(fftData[i]);
}
// find the range of values produced, so we can scale our rendering to
// show up the detail clearly
auto maxLevel = FloatVectorOperations::findMinAndMax (magnitude, fftSize);
for (auto y = 1; y < imageHeight; ++y)
{
// auto skewedProportionY = 1.0f - std::exp (std::log (y / (float) imageHeight) * 0.2f);
// auto fftDataIndex = jlimit (0, fftSize, (int) (skewedProportionY * fftSize));
// auto level = jmap (fftDataForward[fftDataIndex], 0.0f, jmax (maxLevel.getEnd(), 1e-5f), 0.0f, 1.0f);
auto skewedProportionY = 1.0f - std::exp (std::log (y / (float) imageHeight) * 0.2f);
auto a = fftSize;
auto b = (int) (skewedProportionY * fftSize);
auto c = b < a ? b : a;
auto fftDataIndex = jmax (0, c);
auto level = jmap (magnitude[fftDataIndex], 0.0f, jmax (maxLevel.getEnd(), 1e-5f), 0.0f, 1.0f);
spectrogramImage.setPixelAt (rightHandEdge, y, Colour::fromHSV (level, 1.0f, level, 1.0f));
}
}
enum
{
fftOrder = 9,
fftSize = 1 << fftOrder
};
private:
//
// UI variables
//
Slider inputGainSlider;
Label inputGainLabel;
Slider ffSlider;
Label ffLabel;
Slider spreadSlider;
Label spreadLabel;
Slider driveSlider;
Label driveLabel;
Label gainLabel;
Label theGainLabel;
Label magnitudeLabel;
Label theMagnitudeLabel;
//float inputGainTest = 0.0;
//float fftMagTest = 0.0;
//
// signal processing variables
//
Random random;
dsp::FFT spectrogramFFT;
dsp::FFT algorithmFFT;
complex
float outputSampleBuffer[fftSize];
complex
complex
complex
float magnitude[fftSize*2];
float phase[fftSize*2];
float floorFraction;
float spread;
float drive;
//
// Spectrogram Image
//
Image spectrogramImage;
complex
complex
int fifoIndex = 0;
bool nextFFTBlockReady = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};