Commit 9ce1096e authored by Michael Zbyszyński's avatar Michael Zbyszyński
Browse files

JUCE example

parent 5ec38cf2
Subproject commit 1162c2a6e89b5adaea2685b3ff2b844aa9cacff4
Subproject commit 6c3d65be199ec74753e61ec026284fd3ebc71faa
Copyright (c) 2016, Goldsmiths Creative Computing (UK, London)
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
Neither the name of Goldsmiths nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
/*
==============================================================================
This file was auto-generated!
It contains the basic startup code for a Juce application.
==============================================================================
*/
#include "../JuceLibraryCode/JuceHeader.h"
Component* createMainContentComponent();
//==============================================================================
class RAPID_JUCEApplication : public JUCEApplication
{
public:
//==============================================================================
RAPID_JUCEApplication() {}
const String getApplicationName() override { return ProjectInfo::projectName; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return true; }
//==============================================================================
void initialise (const String& commandLine) override
{
// This method is where you should put your application's initialisation code..
mainWindow = new MainWindow (getApplicationName());
}
void shutdown() override
{
// Add your application's shutdown code here..
mainWindow = nullptr; // (deletes our window)
}
//==============================================================================
void systemRequestedQuit() override
{
// This is called when the app is being asked to quit: you can ignore this
// request and let the app carry on running, or call quit() to allow the app to close.
quit();
}
void anotherInstanceStarted (const String& commandLine) override
{
// When another instance of the app is launched while this one is running,
// this method is invoked, and the commandLine parameter tells you what
// the other instance's command-line arguments were.
}
//==============================================================================
/*
This class implements the desktop window that contains an instance of
our MainContentComponent class.
*/
class MainWindow : public DocumentWindow
{
public:
MainWindow (String name) : DocumentWindow (name,
Colours::lightgrey,
DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (createMainContentComponent(), true);
setResizable (true, true);
centreWithSize (getWidth(), getHeight());
setVisible (true);
}
void closeButtonPressed() override
{
// This is called when the user tries to close this window. Here, we'll just
// ask the app to quit when this happens, but you can change this to do
// whatever you need.
JUCEApplication::getInstance()->systemRequestedQuit();
}
/* Note: Be careful if you override any DocumentWindow methods - the base
class uses a lot of them, so by overriding you might break its functionality.
It's best to do all your work in your content component instead, but if
you really have to override any DocumentWindow methods, make sure your
subclass also calls the superclass's method.
*/
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
private:
ScopedPointer<MainWindow> mainWindow;
};
//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (RAPID_JUCEApplication)
/*
==============================================================================
This file was auto-generated!
==============================================================================
*/
#ifndef MAINCOMPONENT_H_INCLUDED
#define MAINCOMPONENT_H_INCLUDED
// It's important to call any headers that might
// include apple libs / headers before Juce's
// to prevent ambigious classes
#include "../../../../dependencies/Maximilian/maximilian.h"
#include "../../../../dependencies/RapidLib/src/regression.h"
#include <random>
#include <array>
#include "../JuceLibraryCode/JuceHeader.h"
class MainContentComponent;
//==============================================================================
class Oscilloscope : public Component,
private Timer
{
public:
Oscilloscope(MainContentComponent* _p) : parent(_p),
writePosition(0),
bufferSize(16384),
paintSize(256)
{
startTimer(40);
}
void pushBuffer(const float* _sampleData, int _numSamples)
{
for (int i = 0; i < _numSamples; ++i)
{
size_t pos = ++writePosition % buffer.size();
oldBuffer[pos] = buffer[pos];
buffer[pos] = _sampleData[i];
}
}
void paint(Graphics& _g) override
{
boundary = getLocalBounds();
_g.fillAll(juce::Colour(0xff000000));
Path path;
path.startNewSubPath(0, .5 * boundary.getHeight());
const float bufferYScale = .3f;
int paintPos = 2;
while (paintPos < buffer.size())
{
if (isZeroCrossing(paintPos))
{
break;
}
++paintPos;
}
const int posOffset = paintPos;
while (paintPos < buffer.size())
{
float bufferPoint = buffer[paintPos];//0.5 * std::abs(buffer[paintPos] - oldBuffer[paintPos]);
Point<float> point((paintPos - posOffset) * boundary.getWidth() / paintSize,
0.5 * ((bufferYScale * bufferPoint) + 1) * boundary.getHeight());
path.lineTo(point);
++paintPos;
}
_g.setColour(juce::Colour(0xff00ff00));
_g.strokePath(path, PathStrokeType(2.0f));
}
void mouseDrag(const MouseEvent& _event) override;
private:
//==========================================================================
void timerCallback() override
{
repaint();
}
bool isZeroCrossing(int i) const noexcept
{
jassert(i > 0);
return buffer[i] > buffer[i - 1] && buffer[i] > 0 && buffer[i - 1] < 0;
}
//==========================================================================
juce::Rectangle<int> boundary;
std::array<float, int((float)44100 / 83)> buffer;
std::array<float, int((float)44100 / 83)> oldBuffer;
std::size_t writePosition;
const int bufferSize;
const int paintSize;
MainContentComponent* const parent;
};
//==============================================================================
class MainContentComponent : public AudioAppComponent,
public Slider::Listener,
public Button::Listener
{
public:
//==========================================================================
/* Setup */
MainContentComponent():
modulationFrequency(4.1),
modulationDepth(.2),
centreFrequency(512),
resonance(2),
targetModulationFrequency(4.1),
targetModulationDepth(.2),
targetCentreFrequency(512),
targetResonance(2),
trained(false),
oscilloscope(this)
{
header.setColour(TextButton::buttonColourId, Colours::wheat);
header.setColour(TextButton::textColourOffId, Colours::white);
header.setEnabled(false);
header.setButtonText("Rapid API Example");
addAndMakeVisible(header);
sidebar.setColour(TextButton::buttonColourId, Colours::white);
sidebar.setColour(TextButton::textColourOffId, Colours::white);
sidebar.setEnabled(false);
addAndMakeVisible(sidebar);
train.setColour(TextButton::buttonColourId, Colours::wheat);
train.setColour(TextButton::textColourOffId, Colours::white);
train.setButtonText("Train");
train.addListener(this);
addAndMakeVisible(train);
randomise.setColour(TextButton::buttonColourId, Colours::wheat);
randomise.setColour(TextButton::textColourOffId, Colours::white);
randomise.setButtonText("Randomise!");
randomise.addListener(this);
addAndMakeVisible(randomise);
footer.setColour(TextButton::buttonColourId, Colours::wheat);
footer.setColour(TextButton::textColourOffId, Colours::white);
footer.setEnabled(false);
footer.setButtonText("Select the synth parameters you like, then move the mouse to an area and hold space to associate that space with that sound. Repeat with a few sounds");
addAndMakeVisible(footer);
modulationFrequencySlider.setRange(0, 4096);
modulationFrequencySlider.setSkewFactorFromMidPoint (500.0);
modulationFrequencySlider.setValue(modulationFrequency, dontSendNotification);
modulationFrequencySlider.addListener(this);
addAndMakeVisible(modulationFrequencySlider);
modulationFrequencyLabel.setText("Mod Frequency", dontSendNotification);
modulationFrequencyLabel.setColour(Label::ColourIds::textColourId, Colours::white);
modulationFrequencyLabel.attachToComponent(&modulationFrequencySlider, false);
addAndMakeVisible(modulationFrequencyLabel);
modulationIndexSlider.setRange(0., 1.);
modulationIndexSlider.setValue(modulationDepth, dontSendNotification);
modulationIndexSlider.addListener(this);
addAndMakeVisible(modulationIndexSlider);
modulationIndexLabel.setText("Mod Index", dontSendNotification);
modulationFrequencyLabel.setColour(Label::ColourIds::textColourId, Colours::white);
modulationIndexLabel.attachToComponent(&modulationIndexSlider, false);
addAndMakeVisible(modulationIndexLabel);
filterFrequencySlider.setRange(0.00001, 4096);
filterFrequencySlider.setColour(Label::ColourIds::textColourId, Colours::white);
filterFrequencySlider.setSkewFactorFromMidPoint (500.0);
filterFrequencySlider.setValue(centreFrequency, dontSendNotification);
filterFrequencySlider.addListener(this);
addAndMakeVisible(filterFrequencySlider);
filterFrequencyLabel.setText("Filter Frequency", dontSendNotification);
filterFrequencyLabel.setColour(Label::ColourIds::textColourId, Colours::white);
filterFrequencyLabel.attachToComponent(&filterFrequencySlider, false);
addAndMakeVisible(filterFrequencyLabel);
filterResonanceSlider.setRange(0.00001, 40);
filterResonanceSlider.setValue(resonance, dontSendNotification);
filterResonanceSlider.addListener(this);
addAndMakeVisible(filterResonanceSlider);
filterResonanceLabel.setText("Filter Resonance", dontSendNotification);
filterResonanceLabel.setColour(Label::ColourIds::textColourId, Colours::white);
filterResonanceLabel.attachToComponent(&filterResonanceSlider, false);
addAndMakeVisible(filterResonanceLabel);
addAndMakeVisible(oscilloscope);
setSize (800, 600);
// No inputs, two outputs
setAudioChannels (0, 2);
}
~MainContentComponent()
{
shutdownAudio();
}
//==========================================================================
/* Audio Methods */
void prepareToPlay (int _samplesPerBlockExpected, double _sampleRate) override
{
// Remember this is part of the audio callback so we must be careful
// not to do anything silly like allocating memory or dare I say wait
// for locks!
maxiSettings::setup(std::round(_sampleRate), 2, _samplesPerBlockExpected);
}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
int periodLength = int(maxiSettings::sampleRate / 83);
// Get the buffers for both output channels
float* const bufferL = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);
float* const bufferR = bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample);
const double localTargetModulationFrequency = targetModulationFrequency;
const double localTargetModulationDepth = targetModulationDepth;
const double localTargetCentreFrequency = targetCentreFrequency;
const double localTargetResonance = targetResonance;
const double modulationFrequencyDelta = (localTargetModulationFrequency != modulationFrequency) ? (targetModulationFrequency - modulationFrequency) / bufferToFill.numSamples : 0;
const double modulationDepthDelta = (localTargetModulationDepth != modulationDepth) ? (targetModulationDepth - modulationDepth) / bufferToFill.numSamples : 0;
const double centreFrequencyDelta = (localTargetCentreFrequency != centreFrequency) ? (targetCentreFrequency - centreFrequency) / bufferToFill.numSamples : 0;
const double resonanceDelta = (localTargetResonance != resonance) ? (targetResonance - resonance) / bufferToFill.numSamples : 0;
// Create our FM Maximilian noises and send the mixed output to the channels
for (int sample = 0; sample < bufferToFill.numSamples; ++sample)
{
modulationFrequency += modulationFrequencyDelta;
modulationDepth += modulationDepthDelta;
centreFrequency += centreFrequencyDelta;
resonance += resonanceDelta;
const double modulatorFrame = (modulator.sinewave(modulationFrequency) + 1.0) / 2.0 * modulationDepth + (1 - modulationDepth);
const double carrierFrame = carrier.pulse(83, modulatorFrame);
const double filteredFrame = filter.lores(carrierFrame, centreFrequency, resonance);
mixer.stereo(filteredFrame, outputs, .5);
bufferL[sample] = float(outputs[0]);
bufferR[sample] = float(outputs[1]);
}
modulationFrequency = localTargetModulationFrequency;
modulationDepth = localTargetModulationDepth;
centreFrequency = localTargetCentreFrequency;
resonance = localTargetResonance;
oscilloscope.pushBuffer(bufferL, bufferToFill.numSamples);
}
void releaseResources() override {}
//==========================================================================
/* Graphics, GUI */
void paint (Graphics& _g) override
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
_g.fillAll (Colour::fromRGB(40, 40, 40));
}
// This is called once at startup and then everytime the window is resized
// Essentially all the code here just subdivides the available window space
void resized() override
{
juce::Rectangle<int> area(getLocalBounds());
const int headerFooterHeight = 36;
header.setBounds(area.removeFromTop(headerFooterHeight));
footer.setBounds(area.removeFromBottom(headerFooterHeight));
const int sidebarWidth = jmax(100, area.getWidth() / 4);
juce::Rectangle<int> sidebarArea(area.removeFromLeft(sidebarWidth));
sidebar.setBounds(sidebarArea);
const int drawingWidth = area.getWidth();
drawingArea = area.removeFromRight(drawingWidth);
oscilloscope.setBounds(drawingArea.reduced(int(sidebarWidth / 4.0)));
const int sideItemHeight = 40;
const int sideItemMargin = 5;
train.setBounds(sidebarArea.removeFromTop(sideItemHeight).reduced(sideItemMargin));
randomise.setBounds(sidebarArea.removeFromBottom(sideItemHeight).reduced(sideItemMargin));
filterResonanceSlider.setBounds(sidebarArea.removeFromBottom(sideItemHeight).reduced(2.5*sideItemMargin));
filterFrequencySlider.setBounds(sidebarArea.removeFromBottom(sideItemHeight).reduced(2.5*sideItemMargin));
modulationIndexSlider.setBounds(sidebarArea.removeFromBottom(sideItemHeight).reduced(2.5*sideItemMargin));
modulationFrequencySlider.setBounds(sidebarArea.removeFromBottom(sideItemHeight).reduced(2.5*sideItemMargin));
}
//==========================================================================
/* Interaction events via mouse and keyboard */
void mouseDrag(const MouseEvent& _event) override
{
if (trained)
{
std::vector<double> input = normaliseMouseSpace(_event.position.roundToInt(), drawingArea);
std::vector<double> output = rapidRegression.run(input);
targetModulationFrequency = output[0];
targetModulationDepth = output[1];
targetCentreFrequency = output[2];
targetResonance = output[3];
modulationFrequencySlider.setValue(targetModulationFrequency, dontSendNotification);
modulationIndexSlider.setValue(targetModulationDepth, dontSendNotification);
filterFrequencySlider.setValue(targetCentreFrequency, dontSendNotification);
filterResonanceSlider.setValue(targetResonance, dontSendNotification);
}
else
{
AlertWindow::showMessageBoxAsync(AlertWindow::AlertIconType::WarningIcon, "Error", "Please train the model before trying to run it!", "ok");
}
}
void mouseMove(const MouseEvent& _event) override
{
if (KeyPress::isKeyCurrentlyDown(KeyPress::spaceKey))
{
std::vector<double> input = normaliseMouseSpace(_event.position.roundToInt(), drawingArea);
trainingExample<double> example;
example.input = { input[0], input[1] };
example.output = { modulationFrequency, modulationDepth, centreFrequency, resonance };
trainingSet.push_back(example);
// only do once...
if (input.size() > 0)
{
footer.setButtonText("When you have finished recorded the sounds you want, press train!");
}
}
}
std::vector<double> normaliseMouseSpace(const juce::Point<int>& _position, const juce::Rectangle<int>& _area)
{
juce::Point<int> pos = _area.getConstrainedPoint(_position);
std::vector<double> temp;
temp.resize(2);
temp[0] = double(double(pos.getX() - _area.getX()) / _area.getWidth());
temp[1] = double(double(pos.getY() - _area.getY()) / _area.getHeight());
return temp;
}
void sliderValueChanged(Slider* _slider) override
{
if (_slider == &modulationFrequencySlider)
targetModulationFrequency = modulationFrequencySlider.getValue();
else if (_slider == &modulationIndexSlider)
targetModulationDepth = modulationIndexSlider.getValue();
else if (_slider == &filterFrequencySlider)
targetCentreFrequency = filterFrequencySlider.getValue();
else if (_slider == &filterResonanceSlider)
targetResonance = filterResonanceSlider.getValue();
}
void buttonClicked(Button* _button) override
{
if (_button == &train)
{
if (trainingSet.size() > 2)
{
trained = rapidRegression.train(trainingSet);
footer.setButtonText("Now just drag the mouse to play!");
}
else
{
AlertWindow::showMessageBoxAsync(AlertWindow::AlertIconType::WarningIcon, "Error", "Please record more audio and mouse coordinate examples before training!\n\nYou can add examples by choosing mouse coordinates with the current sound and holding space whilst wiggling the mouse", "ok");
}
}
else if (_button == &randomise)
{
std::random_device random;
std::default_random_engine generator(random());
std::uniform_real_distribution<double> distribution(0.0, 1.0);
targetModulationFrequency = 4096 * distribution(generator);
targetModulationDepth = distribution(generator);
targetCentreFrequency = 4096 * distribution(generator);
targetResonance = 40 * distribution(generator);
modulationFrequencySlider.setValue(targetModulationFrequency, dontSendNotification);
modulationIndexSlider.setValue(targetModulationDepth, dontSendNotification);
filterFrequencySlider.setValue(targetCentreFrequency, dontSendNotification);
filterResonanceSlider.setValue(targetResonance, dontSendNotification);
}
}
private:
//==========================================================================
/*** AUDIO ***/
// FM Synthesis private parameters
double modulationFrequency;
double modulationDepth;
double centreFrequency;
double resonance;
double targetModulationFrequency;
double targetModulationDepth;
double targetCentreFrequency;
double targetResonance;
double outputs[2];
// Maximilian objects
maxiOsc carrier;
maxiOsc modulator;
maxiFilter filter;
maxiMix mixer;
//==========================================================================
/*** MACHINE LEARNING ***/
// Rapid regression
regression<double> rapidRegression;
std::vector<trainingExample<double> > trainingSet;
// Program state
bool trained;
//==========================================================================
/*** GUI ***/
// Layout
TextButton header;
TextButton sidebar;
TextButton footer;
juce::Rectangle<int> drawingArea;
// Rapidmix
TextButton train;
// Maximilian
Slider modulationFrequencySlider;
Label modulationFrequencyLabel;
Slider modulationIndexSlider;
Label modulationIndexLabel;
Slider filterFrequencySlider;
Label filterFrequencyLabel;
Slider filterResonanceSlider;
Label filterResonanceLabel;
TextButton randomise;
//==========================================================================
/*** GRAPHICS ***/
Oscilloscope oscilloscope;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
void Oscilloscop