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.

318 lines
11KB

  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 "../JuceDemoHeader.h"
  20. #include "AudioLiveScrollingDisplay.h"
  21. //==============================================================================
  22. /** Our demo synth sound is just a basic sine wave.. */
  23. struct SineWaveSound : public SynthesiserSound
  24. {
  25. SineWaveSound() {}
  26. bool appliesToNote (int /*midiNoteNumber*/) override { return true; }
  27. bool appliesToChannel (int /*midiChannel*/) override { return true; }
  28. };
  29. //==============================================================================
  30. /** Our demo synth voice just plays a sine wave.. */
  31. struct SineWaveVoice : public SynthesiserVoice
  32. {
  33. SineWaveVoice() : currentAngle (0), angleDelta (0), level (0), tailOff (0)
  34. {
  35. }
  36. bool canPlaySound (SynthesiserSound* sound) override
  37. {
  38. return dynamic_cast<SineWaveSound*> (sound) != nullptr;
  39. }
  40. void startNote (int midiNoteNumber, float velocity,
  41. SynthesiserSound*, int /*currentPitchWheelPosition*/) override
  42. {
  43. currentAngle = 0.0;
  44. level = velocity * 0.15;
  45. tailOff = 0.0;
  46. double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
  47. double cyclesPerSample = cyclesPerSecond / getSampleRate();
  48. angleDelta = cyclesPerSample * 2.0 * double_Pi;
  49. }
  50. void stopNote (float /*velocity*/, bool allowTailOff) override
  51. {
  52. if (allowTailOff)
  53. {
  54. // start a tail-off by setting this flag. The render callback will pick up on
  55. // this and do a fade out, calling clearCurrentNote() when it's finished.
  56. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  57. // stopNote method could be called more than once.
  58. tailOff = 1.0;
  59. }
  60. else
  61. {
  62. // we're being told to stop playing immediately, so reset everything..
  63. clearCurrentNote();
  64. angleDelta = 0.0;
  65. }
  66. }
  67. void pitchWheelMoved (int /*newValue*/) override
  68. {
  69. // can't be bothered implementing this for the demo!
  70. }
  71. void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
  72. {
  73. // not interested in controllers in this case.
  74. }
  75. void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override
  76. {
  77. if (angleDelta != 0.0)
  78. {
  79. if (tailOff > 0)
  80. {
  81. while (--numSamples >= 0)
  82. {
  83. const float currentSample = (float) (std::sin (currentAngle) * level * tailOff);
  84. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  85. outputBuffer.addSample (i, startSample, currentSample);
  86. currentAngle += angleDelta;
  87. ++startSample;
  88. tailOff *= 0.99;
  89. if (tailOff <= 0.005)
  90. {
  91. clearCurrentNote();
  92. angleDelta = 0.0;
  93. break;
  94. }
  95. }
  96. }
  97. else
  98. {
  99. while (--numSamples >= 0)
  100. {
  101. const float currentSample = (float) (std::sin (currentAngle) * level);
  102. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  103. outputBuffer.addSample (i, startSample, currentSample);
  104. currentAngle += angleDelta;
  105. ++startSample;
  106. }
  107. }
  108. }
  109. }
  110. private:
  111. double currentAngle, angleDelta, level, tailOff;
  112. };
  113. //==============================================================================
  114. // This is an audio source that streams the output of our demo synth.
  115. struct SynthAudioSource : public AudioSource
  116. {
  117. SynthAudioSource (MidiKeyboardState& keyState) : keyboardState (keyState)
  118. {
  119. // Add some voices to our synth, to play the sounds..
  120. for (int i = 4; --i >= 0;)
  121. {
  122. synth.addVoice (new SineWaveVoice()); // These voices will play our custom sine-wave sounds..
  123. synth.addVoice (new SamplerVoice()); // and these ones play the sampled sounds
  124. }
  125. // ..and add a sound for them to play...
  126. setUsingSineWaveSound();
  127. }
  128. void setUsingSineWaveSound()
  129. {
  130. synth.clearSounds();
  131. synth.addSound (new SineWaveSound());
  132. }
  133. void setUsingSampledSound()
  134. {
  135. WavAudioFormat wavFormat;
  136. ScopedPointer<AudioFormatReader> audioReader (wavFormat.createReaderFor (new MemoryInputStream (BinaryData::cello_wav,
  137. BinaryData::cello_wavSize,
  138. false),
  139. true));
  140. BigInteger allNotes;
  141. allNotes.setRange (0, 128, true);
  142. synth.clearSounds();
  143. synth.addSound (new SamplerSound ("demo sound",
  144. *audioReader,
  145. allNotes,
  146. 74, // root midi note
  147. 0.1, // attack time
  148. 0.1, // release time
  149. 10.0 // maximum sample length
  150. ));
  151. }
  152. void prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate) override
  153. {
  154. midiCollector.reset (sampleRate);
  155. synth.setCurrentPlaybackSampleRate (sampleRate);
  156. }
  157. void releaseResources() override
  158. {
  159. }
  160. void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
  161. {
  162. // the synth always adds its output to the audio buffer, so we have to clear it
  163. // first..
  164. bufferToFill.clearActiveBufferRegion();
  165. // fill a midi buffer with incoming messages from the midi input.
  166. MidiBuffer incomingMidi;
  167. midiCollector.removeNextBlockOfMessages (incomingMidi, bufferToFill.numSamples);
  168. // pass these messages to the keyboard state so that it can update the component
  169. // to show on-screen which keys are being pressed on the physical midi keyboard.
  170. // This call will also add midi messages to the buffer which were generated by
  171. // the mouse-clicking on the on-screen keyboard.
  172. keyboardState.processNextMidiBuffer (incomingMidi, 0, bufferToFill.numSamples, true);
  173. // and now get the synth to process the midi events and generate its output.
  174. synth.renderNextBlock (*bufferToFill.buffer, incomingMidi, 0, bufferToFill.numSamples);
  175. }
  176. //==============================================================================
  177. // this collects real-time midi messages from the midi input device, and
  178. // turns them into blocks that we can process in our audio callback
  179. MidiMessageCollector midiCollector;
  180. // this represents the state of which keys on our on-screen keyboard are held
  181. // down. When the mouse is clicked on the keyboard component, this object also
  182. // generates midi messages for this, which we can pass on to our synth.
  183. MidiKeyboardState& keyboardState;
  184. // the synth itself!
  185. Synthesiser synth;
  186. };
  187. //==============================================================================
  188. class AudioSynthesiserDemo : public Component,
  189. private Button::Listener
  190. {
  191. public:
  192. AudioSynthesiserDemo()
  193. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  194. synthAudioSource (keyboardState),
  195. keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard)
  196. {
  197. addAndMakeVisible (keyboardComponent);
  198. addAndMakeVisible (sineButton);
  199. sineButton.setButtonText ("Use sine wave");
  200. sineButton.setRadioGroupId (321);
  201. sineButton.addListener (this);
  202. sineButton.setToggleState (true, dontSendNotification);
  203. addAndMakeVisible (sampledButton);
  204. sampledButton.setButtonText ("Use sampled sound");
  205. sampledButton.setRadioGroupId (321);
  206. sampledButton.addListener (this);
  207. addAndMakeVisible (liveAudioDisplayComp);
  208. deviceManager.addAudioCallback (&liveAudioDisplayComp);
  209. audioSourcePlayer.setSource (&synthAudioSource);
  210. deviceManager.addAudioCallback (&audioSourcePlayer);
  211. deviceManager.addMidiInputCallback (String(), &(synthAudioSource.midiCollector));
  212. setOpaque (true);
  213. setSize (640, 480);
  214. }
  215. ~AudioSynthesiserDemo()
  216. {
  217. audioSourcePlayer.setSource (nullptr);
  218. deviceManager.removeMidiInputCallback (String(), &(synthAudioSource.midiCollector));
  219. deviceManager.removeAudioCallback (&audioSourcePlayer);
  220. deviceManager.removeAudioCallback (&liveAudioDisplayComp);
  221. }
  222. //==============================================================================
  223. void paint (Graphics& g) override
  224. {
  225. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  226. }
  227. void resized() override
  228. {
  229. keyboardComponent.setBounds (8, 96, getWidth() - 16, 64);
  230. sineButton.setBounds (16, 176, 150, 24);
  231. sampledButton.setBounds (16, 200, 150, 24);
  232. liveAudioDisplayComp.setBounds (8, 8, getWidth() - 16, 64);
  233. }
  234. private:
  235. AudioDeviceManager& deviceManager;
  236. MidiKeyboardState keyboardState;
  237. AudioSourcePlayer audioSourcePlayer;
  238. SynthAudioSource synthAudioSource;
  239. MidiKeyboardComponent keyboardComponent;
  240. ToggleButton sineButton;
  241. ToggleButton sampledButton;
  242. LiveScrollingAudioDisplay liveAudioDisplayComp;
  243. //==============================================================================
  244. void buttonClicked (Button* buttonThatWasClicked) override
  245. {
  246. if (buttonThatWasClicked == &sineButton)
  247. synthAudioSource.setUsingSineWaveSound();
  248. else if (buttonThatWasClicked == &sampledButton)
  249. synthAudioSource.setUsingSampledSound();
  250. }
  251. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSynthesiserDemo)
  252. };
  253. // This static object will register this demo type in a global list of demos..
  254. static JuceDemoType<AudioSynthesiserDemo> demo ("31 Audio: Synthesisers");