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.

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