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.

384 lines
13KB

  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 (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  120. .withOutput ("Output", AudioChannelSet::stereo(), true)),
  121. lastUIWidth (400),
  122. lastUIHeight (200),
  123. gainParam (nullptr),
  124. delayParam (nullptr),
  125. delayPosition (0)
  126. {
  127. lastPosInfo.resetToDefault();
  128. // This creates our parameters. We'll keep some raw pointers to them in this class,
  129. // so that we can easily access them later, but the base class will take care of
  130. // deleting them for us.
  131. addParameter (gainParam = new AudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.9f));
  132. addParameter (delayParam = new AudioParameterFloat ("delay", "Delay Feedback", 0.0f, 1.0f, 0.5f));
  133. initialiseSynth();
  134. }
  135. JuceDemoPluginAudioProcessor::~JuceDemoPluginAudioProcessor()
  136. {
  137. }
  138. void JuceDemoPluginAudioProcessor::initialiseSynth()
  139. {
  140. const int numVoices = 8;
  141. // Add some voices...
  142. for (int i = numVoices; --i >= 0;)
  143. synth.addVoice (new SineWaveVoice());
  144. // ..and give the synth a sound to play
  145. synth.addSound (new SineWaveSound());
  146. }
  147. //==============================================================================
  148. bool JuceDemoPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
  149. {
  150. // Only mono/stereo and input/output must have same layout
  151. const AudioChannelSet& mainInput = layouts.getMainInputChannelSet();
  152. const AudioChannelSet& mainOutput = layouts.getMainOutputChannelSet();
  153. // input and output layout must be the same
  154. if (mainInput != mainOutput) return false;
  155. // do not allow disabling the main buses
  156. if (mainInput.isDisabled()) return false;
  157. // only allow stereo and mono
  158. if (mainInput.size() > 2) return false;
  159. return true;
  160. }
  161. void JuceDemoPluginAudioProcessor::prepareToPlay (double newSampleRate, int /*samplesPerBlock*/)
  162. {
  163. // Use this method as the place to do any pre-playback
  164. // initialisation that you need..
  165. synth.setCurrentPlaybackSampleRate (newSampleRate);
  166. keyboardState.reset();
  167. if (isUsingDoublePrecision())
  168. {
  169. delayBufferDouble.setSize (2, 12000);
  170. delayBufferFloat.setSize (1, 1);
  171. }
  172. else
  173. {
  174. delayBufferFloat.setSize (2, 12000);
  175. delayBufferDouble.setSize (1, 1);
  176. }
  177. reset();
  178. }
  179. void JuceDemoPluginAudioProcessor::releaseResources()
  180. {
  181. // When playback stops, you can use this as an opportunity to free up any
  182. // spare memory, etc.
  183. keyboardState.reset();
  184. }
  185. void JuceDemoPluginAudioProcessor::reset()
  186. {
  187. // Use this method as the place to clear any delay lines, buffers, etc, as it
  188. // means there's been a break in the audio's continuity.
  189. delayBufferFloat.clear();
  190. delayBufferDouble.clear();
  191. }
  192. template <typename FloatType>
  193. void JuceDemoPluginAudioProcessor::process (AudioBuffer<FloatType>& buffer,
  194. MidiBuffer& midiMessages,
  195. AudioBuffer<FloatType>& delayBuffer)
  196. {
  197. const int numSamples = buffer.getNumSamples();
  198. // apply our gain-change to the incoming data..
  199. applyGain (buffer, delayBuffer);
  200. // Now pass any incoming midi messages to our keyboard state object, and let it
  201. // add messages to the buffer if the user is clicking on the on-screen keys
  202. keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
  203. // and now get our synth to process these midi events and generate its output.
  204. synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
  205. // Apply our delay effect to the new output..
  206. applyDelay (buffer, delayBuffer);
  207. // In case we have more outputs than inputs, we'll clear any output
  208. // channels that didn't contain input data, (because these aren't
  209. // guaranteed to be empty - they may contain garbage).
  210. for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
  211. buffer.clear (i, 0, numSamples);
  212. // Now ask the host for the current time so we can store it to be displayed later...
  213. updateCurrentTimeInfoFromHost();
  214. }
  215. template <typename FloatType>
  216. void JuceDemoPluginAudioProcessor::applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer)
  217. {
  218. ignoreUnused (delayBuffer);
  219. const float gainLevel = *gainParam;
  220. for (int channel = 0; channel < getTotalNumInputChannels(); ++channel)
  221. buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel);
  222. }
  223. template <typename FloatType>
  224. void JuceDemoPluginAudioProcessor::applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer)
  225. {
  226. const int numSamples = buffer.getNumSamples();
  227. const float delayLevel = *delayParam;
  228. int delayPos = 0;
  229. for (int channel = 0; channel < getTotalNumInputChannels(); ++channel)
  230. {
  231. FloatType* const channelData = buffer.getWritePointer (channel);
  232. FloatType* const delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1));
  233. delayPos = delayPosition;
  234. for (int i = 0; i < numSamples; ++i)
  235. {
  236. const FloatType in = channelData[i];
  237. channelData[i] += delayData[delayPos];
  238. delayData[delayPos] = (delayData[delayPos] + in) * delayLevel;
  239. if (++delayPos >= delayBuffer.getNumSamples())
  240. delayPos = 0;
  241. }
  242. }
  243. delayPosition = delayPos;
  244. }
  245. void JuceDemoPluginAudioProcessor::updateCurrentTimeInfoFromHost()
  246. {
  247. if (AudioPlayHead* ph = getPlayHead())
  248. {
  249. AudioPlayHead::CurrentPositionInfo newTime;
  250. if (ph->getCurrentPosition (newTime))
  251. {
  252. lastPosInfo = newTime; // Successfully got the current time from the host..
  253. return;
  254. }
  255. }
  256. // If the host fails to provide the current time, we'll just reset our copy to a default..
  257. lastPosInfo.resetToDefault();
  258. }
  259. //==============================================================================
  260. AudioProcessorEditor* JuceDemoPluginAudioProcessor::createEditor()
  261. {
  262. return new JuceDemoPluginAudioProcessorEditor (*this);
  263. }
  264. //==============================================================================
  265. void JuceDemoPluginAudioProcessor::getStateInformation (MemoryBlock& destData)
  266. {
  267. // You should use this method to store your parameters in the memory block.
  268. // Here's an example of how you can use XML to make it easy and more robust:
  269. // Create an outer XML element..
  270. XmlElement xml ("MYPLUGINSETTINGS");
  271. // add some attributes to it..
  272. xml.setAttribute ("uiWidth", lastUIWidth);
  273. xml.setAttribute ("uiHeight", lastUIHeight);
  274. // Store the values of all our parameters, using their param ID as the XML attribute
  275. for (int i = 0; i < getNumParameters(); ++i)
  276. if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
  277. xml.setAttribute (p->paramID, p->getValue());
  278. // then use this helper function to stuff it into the binary blob and return it..
  279. copyXmlToBinary (xml, destData);
  280. }
  281. void JuceDemoPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
  282. {
  283. // You should use this method to restore your parameters from this memory block,
  284. // whose contents will have been created by the getStateInformation() call.
  285. // This getXmlFromBinary() helper function retrieves our XML from the binary blob..
  286. ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
  287. if (xmlState != nullptr)
  288. {
  289. // make sure that it's actually our type of XML object..
  290. if (xmlState->hasTagName ("MYPLUGINSETTINGS"))
  291. {
  292. // ok, now pull out our last window size..
  293. lastUIWidth = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400);
  294. lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 200);
  295. // Now reload our parameters..
  296. for (int i = 0; i < getNumParameters(); ++i)
  297. if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
  298. p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue()));
  299. }
  300. }
  301. }
  302. //==============================================================================
  303. // This creates new instances of the plugin..
  304. AudioProcessor* JUCE_CALLTYPE createPluginFilter()
  305. {
  306. return new JuceDemoPluginAudioProcessor();
  307. }