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.

642 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-7 by Raw Material Software ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the
  7. GNU General Public License, as published by the Free Software Foundation;
  8. either version 2 of the License, or (at your option) any later version.
  9. JUCE is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with JUCE; if not, visit www.gnu.org/licenses or write to the
  15. Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  16. Boston, MA 02111-1307 USA
  17. ------------------------------------------------------------------------------
  18. If you'd like to release a closed-source product which uses JUCE, commercial
  19. licenses are also available: visit www.rawmaterialsoftware.com/juce for
  20. more information.
  21. ==============================================================================
  22. */
  23. #include "../jucedemo_headers.h"
  24. //==============================================================================
  25. /** Our demo synth only has one type of sound, and it's very basic..
  26. */
  27. class SineWaveSound : public SynthesiserSound
  28. {
  29. public:
  30. SineWaveSound (const BitArray& midiNotes_)
  31. : midiNotes (midiNotes_)
  32. {
  33. }
  34. bool appliesToNote (const int midiNoteNumber)
  35. {
  36. return midiNotes [midiNoteNumber];
  37. }
  38. bool appliesToChannel (const int midiChannel) { return true; }
  39. private:
  40. // this will contain the notes that this sound is attached to.
  41. BitArray midiNotes;
  42. };
  43. //==============================================================================
  44. /** Our demo synth voice just plays a sine wave..
  45. */
  46. class SineWaveVoice : public SynthesiserVoice
  47. {
  48. public:
  49. SineWaveVoice()
  50. : angleDelta (0.0),
  51. tailOff (0.0)
  52. {
  53. }
  54. bool canPlaySound (SynthesiserSound* sound)
  55. {
  56. return dynamic_cast <SineWaveSound*> (sound) != 0;
  57. }
  58. void startNote (const int midiNoteNumber, const float velocity,
  59. SynthesiserSound* sound, const int currentPitchWheelPosition)
  60. {
  61. currentAngle = 0.0;
  62. level = velocity * 0.15;
  63. tailOff = 0.0;
  64. double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
  65. double cyclesPerSample = cyclesPerSecond / getSampleRate();
  66. angleDelta = cyclesPerSample * 2.0 * double_Pi;
  67. }
  68. void stopNote (const bool allowTailOff)
  69. {
  70. if (allowTailOff)
  71. {
  72. // start a tail-off by setting this flag. The render callback will pick up on
  73. // this and do a fade out, calling clearCurrentNote() when it's finished.
  74. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  75. // stopNote method could be called more than once.
  76. tailOff = 1.0;
  77. }
  78. else
  79. {
  80. // we're being told to stop playing immediately, so reset everything..
  81. clearCurrentNote();
  82. angleDelta = 0.0;
  83. }
  84. }
  85. void pitchWheelMoved (const int newValue)
  86. {
  87. // can't be bothered implementing this for the demo!
  88. }
  89. void controllerMoved (const int controllerNumber, const int newValue)
  90. {
  91. // not interested in controllers in this case.
  92. }
  93. void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples)
  94. {
  95. if (angleDelta != 0.0)
  96. {
  97. if (tailOff > 0)
  98. {
  99. while (--numSamples >= 0)
  100. {
  101. const float currentSample = (float) (sin (currentAngle) * level * tailOff);
  102. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  103. *outputBuffer.getSampleData (i, startSample) += currentSample;
  104. currentAngle += angleDelta;
  105. ++startSample;
  106. tailOff *= 0.99;
  107. if (tailOff <= 0.005)
  108. {
  109. clearCurrentNote();
  110. angleDelta = 0.0;
  111. break;
  112. }
  113. }
  114. }
  115. else
  116. {
  117. while (--numSamples >= 0)
  118. {
  119. const float currentSample = (float) (sin (currentAngle) * level);
  120. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  121. *outputBuffer.getSampleData (i, startSample) += currentSample;
  122. currentAngle += angleDelta;
  123. ++startSample;
  124. }
  125. }
  126. }
  127. }
  128. private:
  129. double currentAngle, angleDelta, level, tailOff;
  130. };
  131. //==============================================================================
  132. /** This is an audio source that streams the output of our demo synth.
  133. */
  134. class SynthAudioSource : public AudioSource
  135. {
  136. public:
  137. //==============================================================================
  138. // this collects real-time midi messages from the midi input device, and
  139. // turns them into blocks that we can process in our audio callback
  140. MidiMessageCollector midiCollector;
  141. // this represents the state of which keys on our on-screen keyboard are held
  142. // down. When the mouse is clicked on the keyboard component, this object also
  143. // generates midi messages for this, which we can pass on to our synth.
  144. MidiKeyboardState keyboardState;
  145. // the synth itself!
  146. Synthesiser synth;
  147. //==============================================================================
  148. SynthAudioSource()
  149. {
  150. // we'll be mixing two different types of sound, so here we'll create two
  151. // sets of note maps, putting each sound on a different octave of the keyboard:
  152. BitArray sinewaveNotes, samplerNotes;
  153. int i;
  154. for (i = 0; i < 128; ++i)
  155. {
  156. if (((i / 12) & 1) != 0)
  157. sinewaveNotes.setBit (i);
  158. else
  159. samplerNotes.setBit (i);
  160. }
  161. // add a wave sound, which will get applied to some of the notes..
  162. synth.addSound (new SineWaveSound (sinewaveNotes));
  163. // give our synth a few voices that can play the wave sound..
  164. for (i = 4; --i >= 0;)
  165. synth.addVoice (new SineWaveVoice());
  166. WavAudioFormat wavFormat;
  167. AudioFormatReader* audioReader
  168. = wavFormat.createReaderFor (new MemoryInputStream (BinaryData::cello_wav,
  169. BinaryData::cello_wavSize,
  170. false),
  171. true);
  172. synth.addSound (new SamplerSound (T("demo sound"),
  173. *audioReader,
  174. samplerNotes,
  175. 74, // root midi note
  176. 0.1, // attack time
  177. 0.1, // release time
  178. 10.0 // maximum sample length
  179. ));
  180. delete audioReader;
  181. // and give the synth some sampler voices to play the sampled sound..
  182. for (i = 4; --i >= 0;)
  183. synth.addVoice (new SamplerVoice());
  184. }
  185. void prepareToPlay (int samplesPerBlockExpected,
  186. double sampleRate)
  187. {
  188. midiCollector.reset (sampleRate);
  189. synth.setCurrentPlaybackSampleRate (sampleRate);
  190. }
  191. void releaseResources()
  192. {
  193. }
  194. void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
  195. {
  196. // the synth always adds its output to the audio buffer, so we have to clear it
  197. // first..
  198. bufferToFill.clearActiveBufferRegion();
  199. // fill a midi buffer with incoming messages from the midi input.
  200. MidiBuffer incomingMidi;
  201. midiCollector.removeNextBlockOfMessages (incomingMidi, bufferToFill.numSamples);
  202. // pass these messages to the keyboard state so that it can update the component
  203. // to show on-screen which keys are being pressed on the physical midi keyboard.
  204. // This call will also add midi messages to the buffer which were generated by
  205. // the mouse-clicking on the on-screen keyboard.
  206. keyboardState.processNextMidiBuffer (incomingMidi, 0, bufferToFill.numSamples, true);
  207. // and now get the synth to process the midi events and generate its output.
  208. synth.renderNextBlock (*bufferToFill.buffer, incomingMidi, 0, bufferToFill.numSamples);
  209. }
  210. };
  211. //==============================================================================
  212. class AudioInputWaveformDisplay : public Component,
  213. public Timer,
  214. public AudioIODeviceCallback
  215. {
  216. public:
  217. AudioInputWaveformDisplay()
  218. {
  219. bufferPos = 0;
  220. bufferSize = 2048;
  221. circularBuffer = (float*) juce_calloc (sizeof (float) * bufferSize);
  222. currentInputLevel = 0.0f;
  223. numSamplesIn = 0;
  224. setOpaque (true);
  225. startTimer (1000 / 50); // repaint every 1/50 of a second
  226. }
  227. ~AudioInputWaveformDisplay()
  228. {
  229. juce_free (circularBuffer);
  230. }
  231. void paint (Graphics& g)
  232. {
  233. g.fillAll (Colours::black);
  234. g.setColour (Colours::lightgreen);
  235. const float halfHeight = getHeight() * 0.5f;
  236. int bp = bufferPos;
  237. for (int x = getWidth(); --x >= 0;)
  238. {
  239. const int samplesAgo = getWidth() - x;
  240. const float level = circularBuffer [(bp + bufferSize - samplesAgo) % bufferSize];
  241. if (level > 0.01f)
  242. g.drawLine ((float) x, halfHeight - halfHeight * level,
  243. (float) x, halfHeight + halfHeight * level);
  244. }
  245. }
  246. void timerCallback()
  247. {
  248. repaint();
  249. }
  250. void addSample (const float sample)
  251. {
  252. currentInputLevel += fabsf (sample);
  253. const int samplesToAverage = 128;
  254. if (++numSamplesIn > samplesToAverage)
  255. {
  256. circularBuffer [bufferPos++ % bufferSize] = currentInputLevel / samplesToAverage;
  257. numSamplesIn = 0;
  258. currentInputLevel = 0.0f;
  259. }
  260. }
  261. void audioDeviceIOCallback (const float** inputChannelData,
  262. int totalNumInputChannels,
  263. float** outputChannelData,
  264. int totalNumOutputChannels,
  265. int numSamples)
  266. {
  267. for (int i = 0; i < totalNumInputChannels; ++i)
  268. {
  269. if (inputChannelData [i] != 0)
  270. {
  271. for (int j = 0; j < numSamples; ++j)
  272. addSample (inputChannelData [i][j]);
  273. break;
  274. }
  275. }
  276. }
  277. void audioDeviceAboutToStart (AudioIODevice*)
  278. {
  279. zeromem (circularBuffer, sizeof (float) * bufferSize);
  280. }
  281. void audioDeviceStopped()
  282. {
  283. zeromem (circularBuffer, sizeof (float) * bufferSize);
  284. }
  285. private:
  286. float* circularBuffer;
  287. float currentInputLevel;
  288. int volatile bufferPos, bufferSize, numSamplesIn;
  289. };
  290. //==============================================================================
  291. class AudioDemo : public Component,
  292. public FilenameComponentListener,
  293. public ButtonListener,
  294. public ChangeListener,
  295. public AudioIODeviceCallback
  296. {
  297. //==============================================================================
  298. FilenameComponent* fileChooser;
  299. TextButton* playButton;
  300. TextButton* stopButton;
  301. TextButton* audioSettingsButton;
  302. MidiKeyboardComponent* keyboardComponent;
  303. AudioInputWaveformDisplay* waveformComponent;
  304. //==============================================================================
  305. // this wraps the actual audio device
  306. AudioDeviceManager audioDeviceManager;
  307. // this allows an audio source to be streamed to the IO device
  308. AudioSourcePlayer audioSourcePlayer;
  309. // this controls the playback of a positionable audio stream, handling the
  310. // starting/stopping and sample-rate conversion
  311. AudioTransportSource transportSource;
  312. // this source contains our synth, and generates its output
  313. SynthAudioSource synthSource;
  314. // this source is used to mix together the output from our synth source
  315. // and wave player source
  316. MixerAudioSource mixerSource;
  317. // this is the actual stream that's going to read from the audio file.
  318. AudioFormatReaderSource* currentAudioFileSource;
  319. File currentFile;
  320. public:
  321. //==============================================================================
  322. AudioDemo()
  323. {
  324. setName (T("Audio"));
  325. currentAudioFileSource = 0;
  326. //==============================================================================
  327. AudioFormatManager formatManager;
  328. formatManager.registerBasicFormats();
  329. addAndMakeVisible (fileChooser = new FilenameComponent (T("audiofile"),
  330. File::nonexistent,
  331. true, false, false,
  332. formatManager.getWildcardForAllFormats(),
  333. String::empty,
  334. T("(choose a WAV or AIFF file to play)")));
  335. fileChooser->addListener (this);
  336. fileChooser->setBrowseButtonText (T("browse"));
  337. addAndMakeVisible (playButton = new TextButton (T("play"),
  338. T("click here to play the current audio file")));
  339. playButton->addButtonListener (this);
  340. playButton->setColour (TextButton::buttonColourId, Colours::lightgreen);
  341. playButton->setColour (TextButton::buttonOnColourId, Colours::lightgreen);
  342. playButton->setConnectedEdges (Button::ConnectedOnRight);
  343. addAndMakeVisible (stopButton = new TextButton (T("stop"),
  344. T("click here to play the current audio file")));
  345. stopButton->addButtonListener (this);
  346. stopButton->setColour (TextButton::buttonColourId, Colours::red);
  347. stopButton->setColour (TextButton::buttonOnColourId, Colours::red);
  348. stopButton->setConnectedEdges (Button::ConnectedOnLeft);
  349. addAndMakeVisible (audioSettingsButton = new TextButton (T("show audio settings..."),
  350. T("click here to change the audio device settings")));
  351. audioSettingsButton->addButtonListener (this);
  352. addAndMakeVisible (keyboardComponent = new MidiKeyboardComponent (synthSource.keyboardState,
  353. MidiKeyboardComponent::horizontalKeyboard));
  354. addAndMakeVisible (waveformComponent = new AudioInputWaveformDisplay());
  355. //==============================================================================
  356. // register for start/stop messages from the transport source..
  357. transportSource.addChangeListener (this);
  358. // and initialise the device manager with no settings so that it picks a
  359. // default device to use.
  360. const String error (audioDeviceManager.initialise (1, /* number of input channels */
  361. 2, /* number of output channels */
  362. 0, /* no XML settings.. */
  363. true /* select default device on failure */));
  364. if (error.isNotEmpty())
  365. {
  366. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  367. T("Audio Demo"),
  368. T("Couldn't open an output device!\n\n") + error);
  369. }
  370. else
  371. {
  372. // add the two audio sources to our mixer..
  373. mixerSource.addInputSource (&transportSource, false);
  374. mixerSource.addInputSource (&synthSource, false);
  375. // ..and connect the mixer to our source player.
  376. audioSourcePlayer.setSource (&mixerSource);
  377. // start the IO device pulling its data from our callback..
  378. audioDeviceManager.setAudioCallback (this);
  379. // and we need to send midi input to our synth for processing
  380. audioDeviceManager.addMidiInputCallback (String::empty, &synthSource.midiCollector);
  381. }
  382. }
  383. ~AudioDemo()
  384. {
  385. audioDeviceManager.removeMidiInputCallback (&synthSource.midiCollector);
  386. audioDeviceManager.setAudioCallback (0);
  387. transportSource.removeChangeListener (this);
  388. transportSource.setSource (0);
  389. deleteAndZero (currentAudioFileSource);
  390. audioSourcePlayer.setSource (0);
  391. deleteAllChildren();
  392. }
  393. //==============================================================================
  394. void audioDeviceIOCallback (const float** inputChannelData,
  395. int totalNumInputChannels,
  396. float** outputChannelData,
  397. int totalNumOutputChannels,
  398. int numSamples)
  399. {
  400. // pass the audio callback on to our player source, and also the waveform display comp
  401. audioSourcePlayer.audioDeviceIOCallback (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples);
  402. waveformComponent->audioDeviceIOCallback (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples);
  403. }
  404. void audioDeviceAboutToStart (AudioIODevice* device)
  405. {
  406. audioSourcePlayer.audioDeviceAboutToStart (device);
  407. waveformComponent->audioDeviceAboutToStart (device);
  408. }
  409. void audioDeviceStopped()
  410. {
  411. audioSourcePlayer.audioDeviceStopped();
  412. waveformComponent->audioDeviceStopped();
  413. }
  414. //==============================================================================
  415. void paint (Graphics& g)
  416. {
  417. // print some text to explain what state we're in.
  418. g.setColour (Colours::black);
  419. g.setFont (14.0f);
  420. String s;
  421. if (transportSource.isPlaying())
  422. s = T("playing");
  423. else
  424. s = T("stopped");
  425. if (currentAudioFileSource == 0)
  426. s += T(" - no source file selected");
  427. else
  428. s += T(" - file: \"") + currentFile.getFullPathName() + T("\"");
  429. g.drawText (s, 250, 50, getWidth() - 250, 24, Justification::centredLeft, true);
  430. }
  431. void resized()
  432. {
  433. fileChooser->setBounds (10, 10, getWidth() - 20, 24);
  434. playButton->setBounds (10, 50, 100, 24);
  435. stopButton->setBounds (110, 50, 100, 24);
  436. audioSettingsButton->setBounds (10, 120, 200, 24);
  437. audioSettingsButton->changeWidthToFitText();
  438. keyboardComponent->setBounds (10, 200, getWidth() - 20, 60);
  439. waveformComponent->setBounds (10, 300, 400, 80);
  440. updateButtons();
  441. }
  442. void updateButtons()
  443. {
  444. playButton->setEnabled (currentAudioFileSource != 0 && ! transportSource.isPlaying());
  445. stopButton->setEnabled (transportSource.isPlaying());
  446. repaint();
  447. }
  448. void buttonClicked (Button* button)
  449. {
  450. if (button == playButton)
  451. {
  452. transportSource.setPosition (0.0);
  453. transportSource.start();
  454. }
  455. else if (button == stopButton)
  456. {
  457. transportSource.stop();
  458. }
  459. else if (button == audioSettingsButton)
  460. {
  461. // Create an AudioDeviceSelectorComponent which contains the audio choice widgets...
  462. AudioDeviceSelectorComponent audioSettingsComp (audioDeviceManager,
  463. 0, 1,
  464. 2, 2,
  465. true,
  466. false);
  467. // ...and show it in a DialogWindow...
  468. audioSettingsComp.setSize (500, 400);
  469. DialogWindow::showModalDialog (T("Audio Settings"),
  470. &audioSettingsComp,
  471. this,
  472. Colours::azure,
  473. true);
  474. }
  475. }
  476. void filenameComponentChanged (FilenameComponent*)
  477. {
  478. // this is called when the user changes the filename in the file chooser box
  479. File audioFile (fileChooser->getCurrentFile());
  480. // unload the previous file source and delete it..
  481. transportSource.stop();
  482. transportSource.setSource (0);
  483. deleteAndZero (currentAudioFileSource);
  484. // create a new file source from the file..
  485. // get a format manager and set it up with the basic types (wav and aiff).
  486. AudioFormatManager formatManager;
  487. formatManager.registerBasicFormats();
  488. AudioFormatReader* reader = formatManager.createReaderFor (audioFile);
  489. if (reader != 0)
  490. {
  491. currentFile = audioFile;
  492. currentAudioFileSource = new AudioFormatReaderSource (reader, true);
  493. // ..and plug it into our transport source
  494. transportSource.setSource (currentAudioFileSource,
  495. 32768, // tells it to buffer this many samples ahead
  496. reader->sampleRate);
  497. }
  498. updateButtons();
  499. }
  500. void changeListenerCallback (void*)
  501. {
  502. // callback from the transport source to tell us that play has
  503. // started or stopped, so update our buttons..
  504. updateButtons();
  505. }
  506. };
  507. //==============================================================================
  508. Component* createAudioDemo()
  509. {
  510. return new AudioDemo();
  511. }