/* ============================================================================== 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 #include #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 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 boundary; std::array buffer; std::array 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-MIX 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 PWM 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 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 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 input = normaliseMouseSpace(_event.position.roundToInt(), drawingArea); std::vector 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 input = normaliseMouseSpace(_event.position.roundToInt(), drawingArea); trainingExample 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 normaliseMouseSpace(const juce::Point& _position, const juce::Rectangle& _area) { juce::Point pos = _area.getConstrainedPoint(_position); std::vector 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 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 rapidRegression; std::vector trainingSet; // Program state bool trained; //========================================================================== /*** GUI ***/ // Layout TextButton header; TextButton sidebar; TextButton footer; juce::Rectangle 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 Oscilloscope::mouseDrag(const MouseEvent& _event) { parent->mouseDrag(_event); } // (This function is called by the app startup code to create our main component) Component* createMainContentComponent() { return new MainContentComponent(); } #endif // MAINCOMPONENT_H_INCLUDED