The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
7.7KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceLibraryCode/JuceHeader.h"
  20. //==============================================================================
  21. class MainContentComponent : public AudioAppComponent,
  22. private Timer
  23. {
  24. public:
  25. //==============================================================================
  26. MainContentComponent()
  27. : pos (299, 299),
  28. waveTableIndex (0),
  29. bufferIndex (0),
  30. sampleRate (0.0),
  31. expectedSamplesPerBlock (0),
  32. dragging (false)
  33. {
  34. setSize (600, 600);
  35. for (int i = 0; i < numElementsInArray (waveValues); ++i)
  36. zeromem (waveValues[i], sizeof (waveValues[i]));
  37. // specify the number of input and output channels that we want to open
  38. setAudioChannels (2, 2);
  39. startTimerHz (60);
  40. }
  41. ~MainContentComponent()
  42. {
  43. shutdownAudio();
  44. }
  45. //==============================================================================
  46. void prepareToPlay (int samplesPerBlockExpected, double newSampleRate) override
  47. {
  48. sampleRate = newSampleRate;
  49. expectedSamplesPerBlock = samplesPerBlockExpected;
  50. }
  51. /* This method generates the actual audio samples.
  52. In this example the buffer is filled with a sine wave whose frequency and
  53. amplitude are controlled by the mouse position.
  54. */
  55. void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
  56. {
  57. bufferToFill.clearActiveBufferRegion();
  58. for (int chan = 0; chan < bufferToFill.buffer->getNumChannels(); ++chan)
  59. {
  60. int ind = waveTableIndex;
  61. float* const channelData = bufferToFill.buffer->getWritePointer (chan, bufferToFill.startSample);
  62. for (int i = 0; i < bufferToFill.numSamples; ++i)
  63. {
  64. if (isPositiveAndBelow (chan, numElementsInArray (waveValues)))
  65. {
  66. channelData[i] = waveValues[chan][ind % wavetableSize];
  67. ++ind;
  68. }
  69. }
  70. }
  71. waveTableIndex = (int) (waveTableIndex + bufferToFill.numSamples) % wavetableSize;
  72. }
  73. void releaseResources() override
  74. {
  75. // This gets automatically called when audio device parameters change
  76. // or device is restarted.
  77. stopTimer();
  78. }
  79. //==============================================================================
  80. void paint (Graphics& g) override
  81. {
  82. // (Our component is opaque, so we must completely fill the background with a solid colour)
  83. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  84. Point<float> nextPos = pos + delta;
  85. if (nextPos.x < 10 || nextPos.x + 10 > getWidth())
  86. {
  87. delta.x = -delta.x;
  88. nextPos.x = pos.x + delta.x;
  89. }
  90. if (nextPos.y < 50 || nextPos.y + 10 > getHeight())
  91. {
  92. delta.y = -delta.y;
  93. nextPos.y = pos.y + delta.y;
  94. }
  95. if (! dragging)
  96. {
  97. writeInterpolatedValue (pos, nextPos);
  98. pos = nextPos;
  99. }
  100. else
  101. {
  102. pos = lastMousePosition;
  103. }
  104. // draw a circle
  105. g.setColour (getLookAndFeel().findColour (Slider::thumbColourId));
  106. g.fillEllipse (pos.x, pos.y, 20, 20);
  107. drawWaveform (g, 20.0f, 0);
  108. drawWaveform (g, 40.0f, 1);
  109. }
  110. void drawWaveform (Graphics& g, float y, int channel) const
  111. {
  112. const int pathWidth = 2000;
  113. Path wavePath;
  114. wavePath.startNewSubPath (0.0f, y);
  115. for (int i = 1; i < pathWidth; ++i)
  116. wavePath.lineTo ((float) i, (1.0f + waveValues[channel][i * numElementsInArray (waveValues[0]) / pathWidth]) * 10.0f);
  117. g.strokePath (wavePath, PathStrokeType (1.0f),
  118. wavePath.getTransformToScaleToFit (Rectangle<float> (0.0f, y, (float) getWidth(), 20.0f), false));
  119. }
  120. // Mouse handling..
  121. void mouseDown (const MouseEvent& e) override
  122. {
  123. lastMousePosition = e.position;
  124. mouseDrag (e);
  125. dragging = true;
  126. }
  127. void mouseDrag (const MouseEvent& e) override
  128. {
  129. dragging = true;
  130. if (e.position != lastMousePosition)
  131. {
  132. // calculate movement vector
  133. delta = e.position - lastMousePosition;
  134. waveValues[0][bufferIndex % wavetableSize] = xToAmplitude (e.position.x);
  135. waveValues[1][bufferIndex % wavetableSize] = yToAmplitude (e.position.y);
  136. ++bufferIndex;
  137. lastMousePosition = e.position;
  138. }
  139. }
  140. void mouseUp (const MouseEvent&) override
  141. {
  142. dragging = false;
  143. }
  144. void writeInterpolatedValue (Point<float> lastPosition,
  145. Point<float> currentPosition)
  146. {
  147. Point<float> start, finish;
  148. if (lastPosition.getX() > currentPosition.getX())
  149. {
  150. finish = lastPosition;
  151. start = currentPosition;
  152. }
  153. else
  154. {
  155. start = lastPosition;
  156. finish = currentPosition;
  157. }
  158. for (int i = 0; i < steps; ++i)
  159. {
  160. Point<float> p = start + ((finish - start) * i) / steps;
  161. const int index = (bufferIndex + i) % wavetableSize;
  162. waveValues[1][index] = yToAmplitude (p.y);
  163. waveValues[0][index] = xToAmplitude (p.x);
  164. }
  165. bufferIndex = (bufferIndex + steps) % wavetableSize;
  166. }
  167. float indexToX (int indexValue) const noexcept
  168. {
  169. return (float) indexValue;
  170. }
  171. float amplitudeToY (float amp) const noexcept
  172. {
  173. return getHeight() - (amp + 1.0f) * getHeight() / 2.0f;
  174. }
  175. float xToAmplitude (float x) const noexcept
  176. {
  177. return jlimit (-1.0f, 1.0f, 2.0f * (getWidth() - x) / getWidth() - 1.0f);
  178. }
  179. float yToAmplitude (float y) const noexcept
  180. {
  181. return jlimit (-1.0f, 1.0f, 2.0f * (getHeight() - y) / getHeight() - 1.0f);
  182. }
  183. void timerCallback() override
  184. {
  185. repaint();
  186. }
  187. private:
  188. //==============================================================================
  189. enum
  190. {
  191. wavetableSize = 36000,
  192. steps = 10
  193. };
  194. Point<float> pos, delta;
  195. int waveTableIndex;
  196. int bufferIndex;
  197. double sampleRate;
  198. int expectedSamplesPerBlock;
  199. Point<float> lastMousePosition;
  200. float waveValues[2][wavetableSize];
  201. bool dragging;
  202. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
  203. };
  204. // (This is called from Main.cpp)
  205. Component* createMainContentComponent() { return new MainContentComponent(); };