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