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.

400 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file was auto-generated by the Jucer!
  4. It contains the basic startup code for a Juce application.
  5. ==============================================================================
  6. */
  7. #include "PluginProcessor.h"
  8. #include "PluginEditor.h"
  9. AudioProcessor* JUCE_CALLTYPE createPluginFilter();
  10. //==============================================================================
  11. /** A demo synth sound that's just a basic sine wave.. */
  12. class SineWaveSound : public SynthesiserSound
  13. {
  14. public:
  15. SineWaveSound() {}
  16. bool appliesToNote (int /*midiNoteNumber*/) override { return true; }
  17. bool appliesToChannel (int /*midiChannel*/) override { return true; }
  18. };
  19. //==============================================================================
  20. /** A simple demo synth voice that just plays a sine wave.. */
  21. class SineWaveVoice : public SynthesiserVoice
  22. {
  23. public:
  24. SineWaveVoice()
  25. : angleDelta (0.0),
  26. tailOff (0.0)
  27. {
  28. }
  29. bool canPlaySound (SynthesiserSound* sound) override
  30. {
  31. return dynamic_cast<SineWaveSound*> (sound) != nullptr;
  32. }
  33. void startNote (int midiNoteNumber, float velocity,
  34. SynthesiserSound* /*sound*/,
  35. int /*currentPitchWheelPosition*/) override
  36. {
  37. currentAngle = 0.0;
  38. level = velocity * 0.15;
  39. tailOff = 0.0;
  40. double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
  41. double cyclesPerSample = cyclesPerSecond / getSampleRate();
  42. angleDelta = cyclesPerSample * 2.0 * double_Pi;
  43. }
  44. void stopNote (float /*velocity*/, bool allowTailOff) override
  45. {
  46. if (allowTailOff)
  47. {
  48. // start a tail-off by setting this flag. The render callback will pick up on
  49. // this and do a fade out, calling clearCurrentNote() when it's finished.
  50. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  51. // stopNote method could be called more than once.
  52. tailOff = 1.0;
  53. }
  54. else
  55. {
  56. // we're being told to stop playing immediately, so reset everything..
  57. clearCurrentNote();
  58. angleDelta = 0.0;
  59. }
  60. }
  61. void pitchWheelMoved (int /*newValue*/) override
  62. {
  63. // can't be bothered implementing this for the demo!
  64. }
  65. void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
  66. {
  67. // not interested in controllers in this case.
  68. }
  69. void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
  70. {
  71. processBlock (outputBuffer, startSample, numSamples);
  72. }
  73. void renderNextBlock (AudioBuffer<double>& outputBuffer, int startSample, int numSamples) override
  74. {
  75. processBlock (outputBuffer, startSample, numSamples);
  76. }
  77. private:
  78. template <typename FloatType>
  79. void processBlock (AudioBuffer<FloatType>& outputBuffer, int startSample, int numSamples)
  80. {
  81. if (angleDelta != 0.0)
  82. {
  83. if (tailOff > 0)
  84. {
  85. while (--numSamples >= 0)
  86. {
  87. const FloatType currentSample =
  88. static_cast<FloatType> (std::sin (currentAngle) * level * tailOff);
  89. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  90. outputBuffer.addSample (i, startSample, currentSample);
  91. currentAngle += angleDelta;
  92. ++startSample;
  93. tailOff *= 0.99;
  94. if (tailOff <= 0.005)
  95. {
  96. clearCurrentNote();
  97. angleDelta = 0.0;
  98. break;
  99. }
  100. }
  101. }
  102. else
  103. {
  104. while (--numSamples >= 0)
  105. {
  106. const FloatType currentSample = static_cast<FloatType> (std::sin (currentAngle) * level);
  107. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  108. outputBuffer.addSample (i, startSample, currentSample);
  109. currentAngle += angleDelta;
  110. ++startSample;
  111. }
  112. }
  113. }
  114. }
  115. double currentAngle, angleDelta, level, tailOff;
  116. };
  117. //==============================================================================
  118. JuceDemoPluginAudioProcessor::JuceDemoPluginAudioProcessor()
  119. : AudioProcessor (getBusesProperties()),
  120. lastUIWidth (400),
  121. lastUIHeight (200),
  122. gainParam (nullptr),
  123. delayParam (nullptr),
  124. delayPosition (0)
  125. {
  126. lastPosInfo.resetToDefault();
  127. // This creates our parameters. We'll keep some raw pointers to them in this class,
  128. // so that we can easily access them later, but the base class will take care of
  129. // deleting them for us.
  130. addParameter (gainParam = new AudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.9f));
  131. addParameter (delayParam = new AudioParameterFloat ("delay", "Delay Feedback", 0.0f, 1.0f, 0.5f));
  132. initialiseSynth();
  133. }
  134. JuceDemoPluginAudioProcessor::~JuceDemoPluginAudioProcessor()
  135. {
  136. }
  137. void JuceDemoPluginAudioProcessor::initialiseSynth()
  138. {
  139. const int numVoices = 8;
  140. // Add some voices...
  141. for (int i = numVoices; --i >= 0;)
  142. synth.addVoice (new SineWaveVoice());
  143. // ..and give the synth a sound to play
  144. synth.addSound (new SineWaveSound());
  145. }
  146. //==============================================================================
  147. bool JuceDemoPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
  148. {
  149. // Only mono/stereo and input/output must have same layout
  150. const AudioChannelSet& mainOutput = layouts.getMainOutputChannelSet();
  151. // input and output layout must be the same
  152. if (wrapperType != wrapperType_Standalone && layouts.getMainInputChannelSet() != mainOutput)
  153. return false;
  154. // do not allow disabling the main buses
  155. if (mainOutput.isDisabled()) return false;
  156. // only allow stereo and mono
  157. if (mainOutput.size() > 2) return false;
  158. return true;
  159. }
  160. AudioProcessor::BusesProperties JuceDemoPluginAudioProcessor::getBusesProperties()
  161. {
  162. // This plug-in should not have any inputs when run as a standalone plug-in
  163. if (PluginHostType::getPluginLoadedAs() == wrapperType_Standalone)
  164. return BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true);
  165. else
  166. return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  167. .withOutput ("Output", AudioChannelSet::stereo(), true);
  168. }
  169. //==============================================================================
  170. void JuceDemoPluginAudioProcessor::prepareToPlay (double newSampleRate, int /*samplesPerBlock*/)
  171. {
  172. // Use this method as the place to do any pre-playback
  173. // initialisation that you need..
  174. synth.setCurrentPlaybackSampleRate (newSampleRate);
  175. keyboardState.reset();
  176. if (isUsingDoublePrecision())
  177. {
  178. delayBufferDouble.setSize (2, 12000);
  179. delayBufferFloat.setSize (1, 1);
  180. }
  181. else
  182. {
  183. delayBufferFloat.setSize (2, 12000);
  184. delayBufferDouble.setSize (1, 1);
  185. }
  186. reset();
  187. }
  188. void JuceDemoPluginAudioProcessor::releaseResources()
  189. {
  190. // When playback stops, you can use this as an opportunity to free up any
  191. // spare memory, etc.
  192. keyboardState.reset();
  193. }
  194. void JuceDemoPluginAudioProcessor::reset()
  195. {
  196. // Use this method as the place to clear any delay lines, buffers, etc, as it
  197. // means there's been a break in the audio's continuity.
  198. delayBufferFloat.clear();
  199. delayBufferDouble.clear();
  200. }
  201. template <typename FloatType>
  202. void JuceDemoPluginAudioProcessor::process (AudioBuffer<FloatType>& buffer,
  203. MidiBuffer& midiMessages,
  204. AudioBuffer<FloatType>& delayBuffer)
  205. {
  206. const int numSamples = buffer.getNumSamples();
  207. if (wrapperType == wrapperType_Standalone)
  208. buffer.clear();
  209. // Now pass any incoming midi messages to our keyboard state object, and let it
  210. // add messages to the buffer if the user is clicking on the on-screen keys
  211. keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
  212. // and now get our synth to process these midi events and generate its output.
  213. synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
  214. // Apply our delay effect to the new output..
  215. applyDelay (buffer, delayBuffer);
  216. if (wrapperType != wrapperType_Standalone)
  217. {
  218. // In case we have more outputs than inputs, we'll clear any output
  219. // channels that didn't contain input data, (because these aren't
  220. // guaranteed to be empty - they may contain garbage).
  221. for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
  222. buffer.clear (i, 0, numSamples);
  223. }
  224. applyGain (buffer, delayBuffer); // apply our gain-change to the outgoing data..
  225. // Now ask the host for the current time so we can store it to be displayed later...
  226. updateCurrentTimeInfoFromHost();
  227. }
  228. template <typename FloatType>
  229. void JuceDemoPluginAudioProcessor::applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer)
  230. {
  231. ignoreUnused (delayBuffer);
  232. const float gainLevel = *gainParam;
  233. for (int channel = 0; channel < getTotalNumOutputChannels(); ++channel)
  234. buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel);
  235. }
  236. template <typename FloatType>
  237. void JuceDemoPluginAudioProcessor::applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer)
  238. {
  239. const int numSamples = buffer.getNumSamples();
  240. const float delayLevel = *delayParam;
  241. int delayPos = 0;
  242. for (int channel = 0; channel < getTotalNumOutputChannels(); ++channel)
  243. {
  244. FloatType* const channelData = buffer.getWritePointer (channel);
  245. FloatType* const delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1));
  246. delayPos = delayPosition;
  247. for (int i = 0; i < numSamples; ++i)
  248. {
  249. const FloatType in = channelData[i];
  250. channelData[i] += delayData[delayPos];
  251. delayData[delayPos] = (delayData[delayPos] + in) * delayLevel;
  252. if (++delayPos >= delayBuffer.getNumSamples())
  253. delayPos = 0;
  254. }
  255. }
  256. delayPosition = delayPos;
  257. }
  258. void JuceDemoPluginAudioProcessor::updateCurrentTimeInfoFromHost()
  259. {
  260. if (AudioPlayHead* ph = getPlayHead())
  261. {
  262. AudioPlayHead::CurrentPositionInfo newTime;
  263. if (ph->getCurrentPosition (newTime))
  264. {
  265. lastPosInfo = newTime; // Successfully got the current time from the host..
  266. return;
  267. }
  268. }
  269. // If the host fails to provide the current time, we'll just reset our copy to a default..
  270. lastPosInfo.resetToDefault();
  271. }
  272. //==============================================================================
  273. AudioProcessorEditor* JuceDemoPluginAudioProcessor::createEditor()
  274. {
  275. return new JuceDemoPluginAudioProcessorEditor (*this);
  276. }
  277. //==============================================================================
  278. void JuceDemoPluginAudioProcessor::getStateInformation (MemoryBlock& destData)
  279. {
  280. // You should use this method to store your parameters in the memory block.
  281. // Here's an example of how you can use XML to make it easy and more robust:
  282. // Create an outer XML element..
  283. XmlElement xml ("MYPLUGINSETTINGS");
  284. // add some attributes to it..
  285. xml.setAttribute ("uiWidth", lastUIWidth);
  286. xml.setAttribute ("uiHeight", lastUIHeight);
  287. // Store the values of all our parameters, using their param ID as the XML attribute
  288. for (int i = 0; i < getNumParameters(); ++i)
  289. if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
  290. xml.setAttribute (p->paramID, p->getValue());
  291. // then use this helper function to stuff it into the binary blob and return it..
  292. copyXmlToBinary (xml, destData);
  293. }
  294. void JuceDemoPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
  295. {
  296. // You should use this method to restore your parameters from this memory block,
  297. // whose contents will have been created by the getStateInformation() call.
  298. // This getXmlFromBinary() helper function retrieves our XML from the binary blob..
  299. ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
  300. if (xmlState != nullptr)
  301. {
  302. // make sure that it's actually our type of XML object..
  303. if (xmlState->hasTagName ("MYPLUGINSETTINGS"))
  304. {
  305. // ok, now pull out our last window size..
  306. lastUIWidth = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400);
  307. lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 200);
  308. // Now reload our parameters..
  309. for (int i = 0; i < getNumParameters(); ++i)
  310. if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
  311. p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue()));
  312. }
  313. }
  314. }
  315. //==============================================================================
  316. // This creates new instances of the plugin..
  317. AudioProcessor* JUCE_CALLTYPE createPluginFilter()
  318. {
  319. return new JuceDemoPluginAudioProcessor();
  320. }