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.

648 lines
24KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: AudioPluginDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Synthesiser audio plugin.
  24. dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
  25. juce_audio_plugin_client, juce_audio_processors,
  26. juce_audio_utils, juce_core, juce_data_structures,
  27. juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
  28. exporters: xcode_mac, vs2017, vs2019, linux_make, xcode_iphone, androidstudio
  29. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  30. type: AudioProcessor
  31. mainClass: JuceDemoPluginAudioProcessor
  32. useLocalCopy: 1
  33. pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn, pluginProducesMidiOut,
  34. pluginEditorRequiresKeys
  35. extraPluginFormats: AUv3
  36. END_JUCE_PIP_METADATA
  37. *******************************************************************************/
  38. #pragma once
  39. //==============================================================================
  40. /** A demo synth sound that's just a basic sine wave.. */
  41. class SineWaveSound : public SynthesiserSound
  42. {
  43. public:
  44. SineWaveSound() {}
  45. bool appliesToNote (int /*midiNoteNumber*/) override { return true; }
  46. bool appliesToChannel (int /*midiChannel*/) override { return true; }
  47. };
  48. //==============================================================================
  49. /** A simple demo synth voice that just plays a sine wave.. */
  50. class SineWaveVoice : public SynthesiserVoice
  51. {
  52. public:
  53. SineWaveVoice() {}
  54. bool canPlaySound (SynthesiserSound* sound) override
  55. {
  56. return dynamic_cast<SineWaveSound*> (sound) != nullptr;
  57. }
  58. void startNote (int midiNoteNumber, float velocity,
  59. SynthesiserSound* /*sound*/,
  60. int /*currentPitchWheelPosition*/) override
  61. {
  62. currentAngle = 0.0;
  63. level = velocity * 0.15;
  64. tailOff = 0.0;
  65. auto cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
  66. auto cyclesPerSample = cyclesPerSecond / getSampleRate();
  67. angleDelta = cyclesPerSample * MathConstants<double>::twoPi;
  68. }
  69. void stopNote (float /*velocity*/, bool allowTailOff) override
  70. {
  71. if (allowTailOff)
  72. {
  73. // start a tail-off by setting this flag. The render callback will pick up on
  74. // this and do a fade out, calling clearCurrentNote() when it's finished.
  75. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  76. // stopNote method could be called more than once.
  77. tailOff = 1.0;
  78. }
  79. else
  80. {
  81. // we're being told to stop playing immediately, so reset everything..
  82. clearCurrentNote();
  83. angleDelta = 0.0;
  84. }
  85. }
  86. void pitchWheelMoved (int /*newValue*/) override
  87. {
  88. // not implemented for the purposes of this demo!
  89. }
  90. void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
  91. {
  92. // not implemented for the purposes of this demo!
  93. }
  94. void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
  95. {
  96. if (angleDelta != 0.0)
  97. {
  98. if (tailOff > 0.0)
  99. {
  100. while (--numSamples >= 0)
  101. {
  102. auto currentSample = (float) (sin (currentAngle) * level * tailOff);
  103. for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
  104. outputBuffer.addSample (i, startSample, currentSample);
  105. currentAngle += angleDelta;
  106. ++startSample;
  107. tailOff *= 0.99;
  108. if (tailOff <= 0.005)
  109. {
  110. // tells the synth that this voice has stopped
  111. clearCurrentNote();
  112. angleDelta = 0.0;
  113. break;
  114. }
  115. }
  116. }
  117. else
  118. {
  119. while (--numSamples >= 0)
  120. {
  121. auto currentSample = (float) (sin (currentAngle) * level);
  122. for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
  123. outputBuffer.addSample (i, startSample, currentSample);
  124. currentAngle += angleDelta;
  125. ++startSample;
  126. }
  127. }
  128. }
  129. }
  130. using SynthesiserVoice::renderNextBlock;
  131. private:
  132. double currentAngle = 0.0;
  133. double angleDelta = 0.0;
  134. double level = 0.0;
  135. double tailOff = 0.0;
  136. };
  137. //==============================================================================
  138. /** As the name suggest, this class does the actual audio processing. */
  139. class JuceDemoPluginAudioProcessor : public AudioProcessor
  140. {
  141. public:
  142. //==============================================================================
  143. JuceDemoPluginAudioProcessor()
  144. : AudioProcessor (getBusesProperties()),
  145. state (*this, nullptr, "state",
  146. { std::make_unique<AudioParameterFloat> ("gain", "Gain", NormalisableRange<float> (0.0f, 1.0f), 0.9f),
  147. std::make_unique<AudioParameterFloat> ("delay", "Delay Feedback", NormalisableRange<float> (0.0f, 1.0f), 0.5f) })
  148. {
  149. lastPosInfo.resetToDefault();
  150. // Add a sub-tree to store the state of our UI
  151. state.state.addChild ({ "uiState", { { "width", 400 }, { "height", 200 } }, {} }, -1, nullptr);
  152. initialiseSynth();
  153. }
  154. ~JuceDemoPluginAudioProcessor() override = default;
  155. //==============================================================================
  156. bool isBusesLayoutSupported (const BusesLayout& layouts) const override
  157. {
  158. // Only mono/stereo and input/output must have same layout
  159. const auto& mainOutput = layouts.getMainOutputChannelSet();
  160. const auto& mainInput = layouts.getMainInputChannelSet();
  161. // input and output layout must either be the same or the input must be disabled altogether
  162. if (! mainInput.isDisabled() && mainInput != mainOutput)
  163. return false;
  164. // do not allow disabling the main buses
  165. if (mainOutput.isDisabled())
  166. return false;
  167. // only allow stereo and mono
  168. if (mainOutput.size() > 2)
  169. return false;
  170. return true;
  171. }
  172. void prepareToPlay (double newSampleRate, int /*samplesPerBlock*/) override
  173. {
  174. // Use this method as the place to do any pre-playback
  175. // initialisation that you need..
  176. synth.setCurrentPlaybackSampleRate (newSampleRate);
  177. keyboardState.reset();
  178. if (isUsingDoublePrecision())
  179. {
  180. delayBufferDouble.setSize (2, 12000);
  181. delayBufferFloat .setSize (1, 1);
  182. }
  183. else
  184. {
  185. delayBufferFloat .setSize (2, 12000);
  186. delayBufferDouble.setSize (1, 1);
  187. }
  188. reset();
  189. }
  190. void releaseResources() override
  191. {
  192. // When playback stops, you can use this as an opportunity to free up any
  193. // spare memory, etc.
  194. keyboardState.reset();
  195. }
  196. void reset() override
  197. {
  198. // Use this method as the place to clear any delay lines, buffers, etc, as it
  199. // means there's been a break in the audio's continuity.
  200. delayBufferFloat .clear();
  201. delayBufferDouble.clear();
  202. }
  203. //==============================================================================
  204. void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
  205. {
  206. jassert (! isUsingDoublePrecision());
  207. process (buffer, midiMessages, delayBufferFloat);
  208. }
  209. void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
  210. {
  211. jassert (isUsingDoublePrecision());
  212. process (buffer, midiMessages, delayBufferDouble);
  213. }
  214. //==============================================================================
  215. bool hasEditor() const override { return true; }
  216. AudioProcessorEditor* createEditor() override
  217. {
  218. return new JuceDemoPluginAudioProcessorEditor (*this);
  219. }
  220. //==============================================================================
  221. const String getName() const override { return "AudioPluginDemo"; }
  222. bool acceptsMidi() const override { return true; }
  223. bool producesMidi() const override { return true; }
  224. double getTailLengthSeconds() const override { return 0.0; }
  225. //==============================================================================
  226. int getNumPrograms() override { return 0; }
  227. int getCurrentProgram() override { return 0; }
  228. void setCurrentProgram (int) override {}
  229. const String getProgramName (int) override { return {}; }
  230. void changeProgramName (int, const String&) override {}
  231. //==============================================================================
  232. void getStateInformation (MemoryBlock& destData) override
  233. {
  234. // Store an xml representation of our state.
  235. if (auto xmlState = state.copyState().createXml())
  236. copyXmlToBinary (*xmlState, destData);
  237. }
  238. void setStateInformation (const void* data, int sizeInBytes) override
  239. {
  240. // Restore our plug-in's state from the xml representation stored in the above
  241. // method.
  242. if (auto xmlState = getXmlFromBinary (data, sizeInBytes))
  243. state.replaceState (ValueTree::fromXml (*xmlState));
  244. }
  245. //==============================================================================
  246. void updateTrackProperties (const TrackProperties& properties) override
  247. {
  248. {
  249. const ScopedLock sl (trackPropertiesLock);
  250. trackProperties = properties;
  251. }
  252. MessageManager::callAsync ([this]
  253. {
  254. if (auto* editor = dynamic_cast<JuceDemoPluginAudioProcessorEditor*> (getActiveEditor()))
  255. editor->updateTrackProperties();
  256. });
  257. }
  258. TrackProperties getTrackProperties() const
  259. {
  260. const ScopedLock sl (trackPropertiesLock);
  261. return trackProperties;
  262. }
  263. //==============================================================================
  264. // These properties are public so that our editor component can access them
  265. // A bit of a hacky way to do it, but it's only a demo! Obviously in your own
  266. // code you'll do this much more neatly..
  267. // this is kept up to date with the midi messages that arrive, and the UI component
  268. // registers with it so it can represent the incoming messages
  269. MidiKeyboardState keyboardState;
  270. // this keeps a copy of the last set of time info that was acquired during an audio
  271. // callback - the UI component will read this and display it.
  272. AudioPlayHead::CurrentPositionInfo lastPosInfo;
  273. // Our plug-in's current state
  274. AudioProcessorValueTreeState state;
  275. private:
  276. //==============================================================================
  277. /** This is the editor component that our filter will display. */
  278. class JuceDemoPluginAudioProcessorEditor : public AudioProcessorEditor,
  279. private Timer,
  280. private Value::Listener
  281. {
  282. public:
  283. JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor& owner)
  284. : AudioProcessorEditor (owner),
  285. midiKeyboard (owner.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
  286. gainAttachment (owner.state, "gain", gainSlider),
  287. delayAttachment (owner.state, "delay", delaySlider)
  288. {
  289. // add some sliders..
  290. addAndMakeVisible (gainSlider);
  291. gainSlider.setSliderStyle (Slider::Rotary);
  292. addAndMakeVisible (delaySlider);
  293. delaySlider.setSliderStyle (Slider::Rotary);
  294. // add some labels for the sliders..
  295. gainLabel.attachToComponent (&gainSlider, false);
  296. gainLabel.setFont (Font (11.0f));
  297. delayLabel.attachToComponent (&delaySlider, false);
  298. delayLabel.setFont (Font (11.0f));
  299. // add the midi keyboard component..
  300. addAndMakeVisible (midiKeyboard);
  301. // add a label that will display the current timecode and status..
  302. addAndMakeVisible (timecodeDisplayLabel);
  303. timecodeDisplayLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 15.0f, Font::plain));
  304. // set resize limits for this plug-in
  305. setResizeLimits (400, 200, 1024, 700);
  306. lastUIWidth .referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("width", nullptr));
  307. lastUIHeight.referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr));
  308. // set our component's initial size to be the last one that was stored in the filter's settings
  309. setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
  310. lastUIWidth. addListener (this);
  311. lastUIHeight.addListener (this);
  312. updateTrackProperties();
  313. // start a timer which will keep our timecode display updated
  314. startTimerHz (30);
  315. }
  316. ~JuceDemoPluginAudioProcessorEditor() override {}
  317. //==============================================================================
  318. void paint (Graphics& g) override
  319. {
  320. g.setColour (backgroundColour);
  321. g.fillAll();
  322. }
  323. void resized() override
  324. {
  325. // This lays out our child components...
  326. auto r = getLocalBounds().reduced (8);
  327. timecodeDisplayLabel.setBounds (r.removeFromTop (26));
  328. midiKeyboard .setBounds (r.removeFromBottom (70));
  329. r.removeFromTop (20);
  330. auto sliderArea = r.removeFromTop (60);
  331. gainSlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth() / 2)));
  332. delaySlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth())));
  333. lastUIWidth = getWidth();
  334. lastUIHeight = getHeight();
  335. }
  336. void timerCallback() override
  337. {
  338. updateTimecodeDisplay (getProcessor().lastPosInfo);
  339. }
  340. void hostMIDIControllerIsAvailable (bool controllerIsAvailable) override
  341. {
  342. midiKeyboard.setVisible (! controllerIsAvailable);
  343. }
  344. int getControlParameterIndex (Component& control) override
  345. {
  346. if (&control == &gainSlider)
  347. return 0;
  348. if (&control == &delaySlider)
  349. return 1;
  350. return -1;
  351. }
  352. void updateTrackProperties()
  353. {
  354. auto trackColour = getProcessor().getTrackProperties().colour;
  355. auto& lf = getLookAndFeel();
  356. backgroundColour = (trackColour == Colour() ? lf.findColour (ResizableWindow::backgroundColourId)
  357. : trackColour.withAlpha (1.0f).withBrightness (0.266f));
  358. repaint();
  359. }
  360. private:
  361. MidiKeyboardComponent midiKeyboard;
  362. Label timecodeDisplayLabel,
  363. gainLabel { {}, "Throughput level:" },
  364. delayLabel { {}, "Delay:" };
  365. Slider gainSlider, delaySlider;
  366. AudioProcessorValueTreeState::SliderAttachment gainAttachment, delayAttachment;
  367. Colour backgroundColour;
  368. // these are used to persist the UI's size - the values are stored along with the
  369. // filter's other parameters, and the UI component will update them when it gets
  370. // resized.
  371. Value lastUIWidth, lastUIHeight;
  372. //==============================================================================
  373. JuceDemoPluginAudioProcessor& getProcessor() const
  374. {
  375. return static_cast<JuceDemoPluginAudioProcessor&> (processor);
  376. }
  377. //==============================================================================
  378. // quick-and-dirty function to format a timecode string
  379. static String timeToTimecodeString (double seconds)
  380. {
  381. auto millisecs = roundToInt (seconds * 1000.0);
  382. auto absMillisecs = std::abs (millisecs);
  383. return String::formatted ("%02d:%02d:%02d.%03d",
  384. millisecs / 3600000,
  385. (absMillisecs / 60000) % 60,
  386. (absMillisecs / 1000) % 60,
  387. absMillisecs % 1000);
  388. }
  389. // quick-and-dirty function to format a bars/beats string
  390. static String quarterNotePositionToBarsBeatsString (double quarterNotes, int numerator, int denominator)
  391. {
  392. if (numerator == 0 || denominator == 0)
  393. return "1|1|000";
  394. auto quarterNotesPerBar = (numerator * 4 / denominator);
  395. auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator;
  396. auto bar = ((int) quarterNotes) / quarterNotesPerBar + 1;
  397. auto beat = ((int) beats) + 1;
  398. auto ticks = ((int) (fmod (beats, 1.0) * 960.0 + 0.5));
  399. return String::formatted ("%d|%d|%03d", bar, beat, ticks);
  400. }
  401. // Updates the text in our position label.
  402. void updateTimecodeDisplay (AudioPlayHead::CurrentPositionInfo pos)
  403. {
  404. MemoryOutputStream displayText;
  405. displayText << "[" << SystemStats::getJUCEVersion() << "] "
  406. << String (pos.bpm, 2) << " bpm, "
  407. << pos.timeSigNumerator << '/' << pos.timeSigDenominator
  408. << " - " << timeToTimecodeString (pos.timeInSeconds)
  409. << " - " << quarterNotePositionToBarsBeatsString (pos.ppqPosition,
  410. pos.timeSigNumerator,
  411. pos.timeSigDenominator);
  412. if (pos.isRecording)
  413. displayText << " (recording)";
  414. else if (pos.isPlaying)
  415. displayText << " (playing)";
  416. timecodeDisplayLabel.setText (displayText.toString(), dontSendNotification);
  417. }
  418. // called when the stored window size changes
  419. void valueChanged (Value&) override
  420. {
  421. setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
  422. }
  423. };
  424. //==============================================================================
  425. template <typename FloatType>
  426. void process (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioBuffer<FloatType>& delayBuffer)
  427. {
  428. auto gainParamValue = state.getParameter ("gain") ->getValue();
  429. auto delayParamValue = state.getParameter ("delay")->getValue();
  430. auto numSamples = buffer.getNumSamples();
  431. // In case we have more outputs than inputs, we'll clear any output
  432. // channels that didn't contain input data, (because these aren't
  433. // guaranteed to be empty - they may contain garbage).
  434. for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
  435. buffer.clear (i, 0, numSamples);
  436. // Now pass any incoming midi messages to our keyboard state object, and let it
  437. // add messages to the buffer if the user is clicking on the on-screen keys
  438. keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
  439. // and now get our synth to process these midi events and generate its output.
  440. synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
  441. // Apply our delay effect to the new output..
  442. applyDelay (buffer, delayBuffer, delayParamValue);
  443. // Apply our gain change to the outgoing data..
  444. applyGain (buffer, delayBuffer, gainParamValue);
  445. // Now ask the host for the current time so we can store it to be displayed later...
  446. updateCurrentTimeInfoFromHost();
  447. }
  448. template <typename FloatType>
  449. void applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float gainLevel)
  450. {
  451. ignoreUnused (delayBuffer);
  452. for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel)
  453. buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel);
  454. }
  455. template <typename FloatType>
  456. void applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float delayLevel)
  457. {
  458. auto numSamples = buffer.getNumSamples();
  459. auto delayPos = 0;
  460. for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel)
  461. {
  462. auto channelData = buffer.getWritePointer (channel);
  463. auto delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1));
  464. delayPos = delayPosition;
  465. for (auto i = 0; i < numSamples; ++i)
  466. {
  467. auto in = channelData[i];
  468. channelData[i] += delayData[delayPos];
  469. delayData[delayPos] = (delayData[delayPos] + in) * delayLevel;
  470. if (++delayPos >= delayBuffer.getNumSamples())
  471. delayPos = 0;
  472. }
  473. }
  474. delayPosition = delayPos;
  475. }
  476. AudioBuffer<float> delayBufferFloat;
  477. AudioBuffer<double> delayBufferDouble;
  478. int delayPosition = 0;
  479. Synthesiser synth;
  480. CriticalSection trackPropertiesLock;
  481. TrackProperties trackProperties;
  482. void initialiseSynth()
  483. {
  484. auto numVoices = 8;
  485. // Add some voices...
  486. for (auto i = 0; i < numVoices; ++i)
  487. synth.addVoice (new SineWaveVoice());
  488. // ..and give the synth a sound to play
  489. synth.addSound (new SineWaveSound());
  490. }
  491. void updateCurrentTimeInfoFromHost()
  492. {
  493. if (auto* ph = getPlayHead())
  494. {
  495. AudioPlayHead::CurrentPositionInfo newTime;
  496. if (ph->getCurrentPosition (newTime))
  497. {
  498. lastPosInfo = newTime; // Successfully got the current time from the host..
  499. return;
  500. }
  501. }
  502. // If the host fails to provide the current time, we'll just reset our copy to a default..
  503. lastPosInfo.resetToDefault();
  504. }
  505. static BusesProperties getBusesProperties()
  506. {
  507. return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  508. .withOutput ("Output", AudioChannelSet::stereo(), true);
  509. }
  510. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceDemoPluginAudioProcessor)
  511. };