/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #include "../JuceLibraryCode/JuceHeader.h" //============================================================================== class MainContentComponent : public AudioAppComponent, private Timer { public: //============================================================================== MainContentComponent() : pos (299, 299), waveTableIndex (0), bufferIndex (0), sampleRate (0.0), expectedSamplesPerBlock (0), dragging (false) { setSize (600, 600); for (int i = 0; i < numElementsInArray (waveValues); ++i) zeromem (waveValues[i], sizeof (waveValues[i])); // specify the number of input and output channels that we want to open setAudioChannels (2, 2); startTimerHz (60); } ~MainContentComponent() { shutdownAudio(); } //============================================================================== void prepareToPlay (int samplesPerBlockExpected, double newSampleRate) override { sampleRate = newSampleRate; expectedSamplesPerBlock = samplesPerBlockExpected; } /* This method generates the actual audio samples. In this example the buffer is filled with a sine wave whose frequency and amplitude are controlled by the mouse position. */ void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override { bufferToFill.clearActiveBufferRegion(); for (int chan = 0; chan < bufferToFill.buffer->getNumChannels(); ++chan) { int ind = waveTableIndex; float* const channelData = bufferToFill.buffer->getWritePointer (chan, bufferToFill.startSample); for (int i = 0; i < bufferToFill.numSamples; ++i) { if (isPositiveAndBelow (chan, numElementsInArray (waveValues))) { channelData[i] = waveValues[chan][ind % wavetableSize]; ++ind; } } } waveTableIndex = (int) (waveTableIndex + bufferToFill.numSamples) % wavetableSize; } void releaseResources() override { // This gets automatically called when audio device parameters change // or device is restarted. stopTimer(); } //============================================================================== void paint (Graphics& g) override { // (Our component is opaque, so we must completely fill the background with a solid colour) g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); Point nextPos = pos + delta; if (nextPos.x < 10 || nextPos.x + 10 > getWidth()) { delta.x = -delta.x; nextPos.x = pos.x + delta.x; } if (nextPos.y < 50 || nextPos.y + 10 > getHeight()) { delta.y = -delta.y; nextPos.y = pos.y + delta.y; } if (! dragging) { writeInterpolatedValue (pos, nextPos); pos = nextPos; } else { pos = lastMousePosition; } // draw a circle g.setColour (getLookAndFeel().findColour (Slider::thumbColourId)); g.fillEllipse (pos.x, pos.y, 20, 20); drawWaveform (g, 20.0f, 0); drawWaveform (g, 40.0f, 1); } void drawWaveform (Graphics& g, float y, int channel) const { const int pathWidth = 2000; Path wavePath; wavePath.startNewSubPath (0.0f, y); for (int i = 1; i < pathWidth; ++i) wavePath.lineTo ((float) i, (1.0f + waveValues[channel][i * numElementsInArray (waveValues[0]) / pathWidth]) * 10.0f); g.strokePath (wavePath, PathStrokeType (1.0f), wavePath.getTransformToScaleToFit (Rectangle (0.0f, y, (float) getWidth(), 20.0f), false)); } // Mouse handling.. void mouseDown (const MouseEvent& e) override { lastMousePosition = e.position; mouseDrag (e); dragging = true; } void mouseDrag (const MouseEvent& e) override { dragging = true; if (e.position != lastMousePosition) { // calculate movement vector delta = e.position - lastMousePosition; waveValues[0][bufferIndex % wavetableSize] = xToAmplitude (e.position.x); waveValues[1][bufferIndex % wavetableSize] = yToAmplitude (e.position.y); ++bufferIndex; lastMousePosition = e.position; } } void mouseUp (const MouseEvent&) override { dragging = false; } void writeInterpolatedValue (Point lastPosition, Point currentPosition) { Point start, finish; if (lastPosition.getX() > currentPosition.getX()) { finish = lastPosition; start = currentPosition; } else { start = lastPosition; finish = currentPosition; } for (int i = 0; i < steps; ++i) { Point p = start + ((finish - start) * i) / steps; const int index = (bufferIndex + i) % wavetableSize; waveValues[1][index] = yToAmplitude (p.y); waveValues[0][index] = xToAmplitude (p.x); } bufferIndex = (bufferIndex + steps) % wavetableSize; } float indexToX (int indexValue) const noexcept { return (float) indexValue; } float amplitudeToY (float amp) const noexcept { return getHeight() - (amp + 1.0f) * getHeight() / 2.0f; } float xToAmplitude (float x) const noexcept { return jlimit (-1.0f, 1.0f, 2.0f * (getWidth() - x) / getWidth() - 1.0f); } float yToAmplitude (float y) const noexcept { return jlimit (-1.0f, 1.0f, 2.0f * (getHeight() - y) / getHeight() - 1.0f); } void timerCallback() override { repaint(); } private: //============================================================================== enum { wavetableSize = 36000, steps = 10 }; Point pos, delta; int waveTableIndex; int bufferIndex; double sampleRate; int expectedSamplesPerBlock; Point lastMousePosition; float waveValues[2][wavetableSize]; bool dragging; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) }; // (This is called from Main.cpp) Component* createMainContentComponent() { return new MainContentComponent(); };