1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE examples.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    The code included in this file is provided under the terms of the ISC license
8    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
9    To use, copy, modify, and/or distribute this software for any purpose with or
10    without fee is hereby granted provided that the above copyright notice and
11    this permission notice appear in all copies.
12 
13    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
14    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
15    PURPOSE, ARE DISCLAIMED.
16 
17   ==============================================================================
18 */
19 
20 /*******************************************************************************
21  The block below describes the properties of this PIP. A PIP is a short snippet
22  of code that can be read by the Projucer and used to generate a JUCE project.
23 
24  BEGIN_JUCE_PIP_METADATA
25 
26  name:             BouncingBallWavetableDemo
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Wavetable synthesis with a bouncing ball.
31 
32  dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats,
33                    juce_audio_processors, juce_audio_utils, juce_core,
34                    juce_data_structures, juce_events, juce_graphics,
35                    juce_gui_basics, juce_gui_extra
36  exporters:        xcode_mac, vs2019, linux_make
37 
38  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
39 
40  type:             Component
41  mainClass:        BouncingBallWavetableDemo
42 
43  useLocalCopy:     1
44 
45  END_JUCE_PIP_METADATA
46 
47 *******************************************************************************/
48 
49 #pragma once
50 
51 
52 //==============================================================================
53 class BouncingBallWavetableDemo   : public AudioAppComponent,
54                                     private Timer
55 {
56 public:
57     //==============================================================================
BouncingBallWavetableDemo()58     BouncingBallWavetableDemo()
59        #ifdef JUCE_DEMO_RUNNER
60         : AudioAppComponent (getSharedAudioDeviceManager (0, 2))
61        #endif
62     {
63         setSize (600, 600);
64 
65         for (auto i = 0; i < numElementsInArray (waveValues); ++i)
66             zeromem (waveValues[i], sizeof (waveValues[i]));
67 
68         // specify the number of input and output channels that we want to open
69         setAudioChannels (2, 2);
70         startTimerHz (60);
71     }
72 
~BouncingBallWavetableDemo()73     ~BouncingBallWavetableDemo() override
74     {
75         shutdownAudio();
76     }
77 
78     //==============================================================================
prepareToPlay(int samplesPerBlockExpected,double newSampleRate)79     void prepareToPlay (int samplesPerBlockExpected, double newSampleRate) override
80     {
81         sampleRate = newSampleRate;
82         expectedSamplesPerBlock = samplesPerBlockExpected;
83     }
84 
85     /*  This method generates the actual audio samples.
86         In this example the buffer is filled with a sine wave whose frequency and
87         amplitude are controlled by the mouse position.
88      */
getNextAudioBlock(const AudioSourceChannelInfo & bufferToFill)89     void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
90     {
91         bufferToFill.clearActiveBufferRegion();
92 
93         for (auto chan = 0; chan < bufferToFill.buffer->getNumChannels(); ++chan)
94         {
95             auto ind = waveTableIndex;
96 
97             auto* channelData = bufferToFill.buffer->getWritePointer (chan, bufferToFill.startSample);
98 
99             for (auto i = 0; i < bufferToFill.numSamples; ++i)
100             {
101                 if (isPositiveAndBelow (chan, numElementsInArray (waveValues)))
102                 {
103                     channelData[i] = waveValues[chan][ind % wavetableSize];
104                     ++ind;
105                 }
106             }
107         }
108 
109         waveTableIndex = (int) (waveTableIndex + bufferToFill.numSamples) % wavetableSize;
110     }
111 
releaseResources()112     void releaseResources() override
113     {
114         // This gets automatically called when audio device parameters change
115         // or device is restarted.
116         stopTimer();
117     }
118 
119 
120     //==============================================================================
paint(Graphics & g)121     void paint (Graphics& g) override
122     {
123         // (Our component is opaque, so we must completely fill the background with a solid colour)
124         g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
125 
126         auto nextPos = pos + delta;
127 
128         if (nextPos.x < 10 || nextPos.x + 10 > (float) getWidth())
129         {
130             delta.x = -delta.x;
131             nextPos.x = pos.x + delta.x;
132         }
133 
134         if (nextPos.y < 50 || nextPos.y + 10 > (float) getHeight())
135         {
136             delta.y = -delta.y;
137             nextPos.y = pos.y + delta.y;
138         }
139 
140         if (! dragging)
141         {
142             writeInterpolatedValue (pos, nextPos);
143             pos = nextPos;
144         }
145         else
146         {
147             pos = lastMousePosition;
148         }
149 
150         // draw a circle
151         g.setColour (getLookAndFeel().findColour (Slider::thumbColourId));
152         g.fillEllipse (pos.x, pos.y, 20, 20);
153 
154         drawWaveform (g, 20.0f, 0);
155         drawWaveform (g, 40.0f, 1);
156     }
157 
drawWaveform(Graphics & g,float y,int channel)158     void drawWaveform (Graphics& g, float y, int channel) const
159     {
160         auto pathWidth = 2000;
161 
162         Path wavePath;
163         wavePath.startNewSubPath (0.0f, y);
164 
165         for (auto i = 1; i < pathWidth; ++i)
166             wavePath.lineTo ((float) i, (1.0f + waveValues[channel][i * numElementsInArray (waveValues[0]) / pathWidth]) * 10.0f);
167 
168         g.strokePath (wavePath, PathStrokeType (1.0f),
169                       wavePath.getTransformToScaleToFit (Rectangle<float> (0.0f, y, (float) getWidth(), 20.0f), false));
170     }
171 
172     // Mouse handling..
mouseDown(const MouseEvent & e)173     void mouseDown (const MouseEvent& e) override
174     {
175         lastMousePosition = e.position;
176         mouseDrag (e);
177         dragging = true;
178     }
179 
mouseDrag(const MouseEvent & e)180     void mouseDrag (const MouseEvent& e) override
181     {
182         dragging = true;
183 
184         if (e.position != lastMousePosition)
185         {
186             // calculate movement vector
187             delta = e.position - lastMousePosition;
188 
189             waveValues[0][bufferIndex % wavetableSize] = xToAmplitude (e.position.x);
190             waveValues[1][bufferIndex % wavetableSize] = yToAmplitude (e.position.y);
191 
192             ++bufferIndex;
193             lastMousePosition = e.position;
194         }
195     }
196 
mouseUp(const MouseEvent &)197     void mouseUp (const MouseEvent&) override
198     {
199         dragging = false;
200     }
201 
writeInterpolatedValue(Point<float> lastPosition,Point<float> currentPosition)202     void writeInterpolatedValue (Point<float> lastPosition,
203                                  Point<float> currentPosition)
204     {
205         Point<float> start, finish;
206 
207         if (lastPosition.getX() > currentPosition.getX())
208         {
209             finish = lastPosition;
210             start  = currentPosition;
211         }
212         else
213         {
214             start  = lastPosition;
215             finish = currentPosition;
216         }
217 
218         for (auto i = 0; i < steps; ++i)
219         {
220             auto p = start + ((finish - start) * i) / (int) steps;
221 
222             auto index = (bufferIndex + i) % wavetableSize;
223             waveValues[1][index] = yToAmplitude (p.y);
224             waveValues[0][index] = xToAmplitude (p.x);
225         }
226 
227         bufferIndex = (bufferIndex + steps) % wavetableSize;
228     }
229 
indexToX(int indexValue)230     float indexToX (int indexValue) const noexcept
231     {
232         return (float) indexValue;
233     }
234 
amplitudeToY(float amp)235     float amplitudeToY (float amp) const noexcept
236     {
237         return (float) getHeight() - (amp + 1.0f) * (float) getHeight() / 2.0f;
238     }
239 
xToAmplitude(float x)240     float xToAmplitude (float x) const noexcept
241     {
242         return jlimit (-1.0f, 1.0f, 2.0f * ((float) getWidth() - x) / (float) getWidth() - 1.0f);
243     }
244 
yToAmplitude(float y)245     float yToAmplitude (float y) const noexcept
246     {
247         return jlimit (-1.0f, 1.0f, 2.0f * ((float) getHeight() - y) / (float) getHeight() - 1.0f);
248     }
249 
timerCallback()250     void timerCallback() override
251     {
252         repaint();
253     }
254 
255 private:
256     //==============================================================================
257     enum
258     {
259         wavetableSize = 36000,
260         steps = 10
261     };
262 
263     Point<float> pos   = { 299.0f, 299.0f };
264     Point<float> delta = { 0.0f, 0.0f };
265     int waveTableIndex = 0;
266     int bufferIndex    = 0;
267     double sampleRate  = 0.0;
268     int expectedSamplesPerBlock = 0;
269     Point<float> lastMousePosition;
270     float waveValues[2][wavetableSize];
271     bool dragging = false;
272 
273     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BouncingBallWavetableDemo)
274 };
275