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.

305 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-12 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../JuceDemoHeader.h"
  19. #include "AudioLiveScrollingDisplay.h"
  20. //==============================================================================
  21. /** A simple class that acts as an AudioIODeviceCallback and writes the
  22. incoming audio data to a WAV file.
  23. */
  24. class AudioRecorder : public AudioIODeviceCallback
  25. {
  26. public:
  27. AudioRecorder (AudioThumbnail& thumbnailToUpdate)
  28. : thumbnail (thumbnailToUpdate),
  29. backgroundThread ("Audio Recorder Thread"),
  30. sampleRate (0), nextSampleNum (0), activeWriter (nullptr)
  31. {
  32. backgroundThread.startThread();
  33. }
  34. ~AudioRecorder()
  35. {
  36. stop();
  37. }
  38. //==============================================================================
  39. void startRecording (const File& file)
  40. {
  41. stop();
  42. if (sampleRate > 0)
  43. {
  44. // Create an OutputStream to write to our destination file...
  45. file.deleteFile();
  46. ScopedPointer<FileOutputStream> fileStream (file.createOutputStream());
  47. if (fileStream != nullptr)
  48. {
  49. // Now create a WAV writer object that writes to our output stream...
  50. WavAudioFormat wavFormat;
  51. AudioFormatWriter* writer = wavFormat.createWriterFor (fileStream, sampleRate, 1, 16, StringPairArray(), 0);
  52. if (writer != nullptr)
  53. {
  54. fileStream.release(); // (passes responsibility for deleting the stream to the writer object that is now using it)
  55. // Now we'll create one of these helper objects which will act as a FIFO buffer, and will
  56. // write the data to disk on our background thread.
  57. threadedWriter = new AudioFormatWriter::ThreadedWriter (writer, backgroundThread, 32768);
  58. // Reset our recording thumbnail
  59. thumbnail.reset (writer->getNumChannels(), writer->getSampleRate());
  60. nextSampleNum = 0;
  61. // And now, swap over our active writer pointer so that the audio callback will start using it..
  62. const ScopedLock sl (writerLock);
  63. activeWriter = threadedWriter;
  64. }
  65. }
  66. }
  67. }
  68. void stop()
  69. {
  70. // First, clear this pointer to stop the audio callback from using our writer object..
  71. {
  72. const ScopedLock sl (writerLock);
  73. activeWriter = nullptr;
  74. }
  75. // Now we can delete the writer object. It's done in this order because the deletion could
  76. // take a little time while remaining data gets flushed to disk, so it's best to avoid blocking
  77. // the audio callback while this happens.
  78. threadedWriter = nullptr;
  79. }
  80. bool isRecording() const
  81. {
  82. return activeWriter != nullptr;
  83. }
  84. //==============================================================================
  85. void audioDeviceAboutToStart (AudioIODevice* device) override
  86. {
  87. sampleRate = device->getCurrentSampleRate();
  88. }
  89. void audioDeviceStopped() override
  90. {
  91. sampleRate = 0;
  92. }
  93. void audioDeviceIOCallback (const float** inputChannelData, int /*numInputChannels*/,
  94. float** outputChannelData, int numOutputChannels,
  95. int numSamples) override
  96. {
  97. const ScopedLock sl (writerLock);
  98. if (activeWriter != nullptr)
  99. {
  100. activeWriter->write (inputChannelData, numSamples);
  101. // Create an AudioSampleBuffer to wrap our incomming data, note that this does no allocations or copies, it simply references our input data
  102. const AudioSampleBuffer buffer (const_cast<float**> (inputChannelData), thumbnail.getNumChannels(), numSamples);
  103. thumbnail.addBlock (nextSampleNum, buffer, 0, numSamples);
  104. nextSampleNum += numSamples;
  105. }
  106. // We need to clear the output buffers, in case they're full of junk..
  107. for (int i = 0; i < numOutputChannels; ++i)
  108. if (outputChannelData[i] != nullptr)
  109. FloatVectorOperations::clear (outputChannelData[i], numSamples);
  110. }
  111. private:
  112. AudioThumbnail& thumbnail;
  113. TimeSliceThread backgroundThread; // the thread that will write our audio data to disk
  114. ScopedPointer<AudioFormatWriter::ThreadedWriter> threadedWriter; // the FIFO used to buffer the incoming data
  115. double sampleRate;
  116. int64 nextSampleNum;
  117. CriticalSection writerLock;
  118. AudioFormatWriter::ThreadedWriter* volatile activeWriter;
  119. };
  120. //==============================================================================
  121. class RecordingThumbnail : public Component,
  122. private ChangeListener
  123. {
  124. public:
  125. RecordingThumbnail()
  126. : thumbnailCache (10),
  127. thumbnail (512, formatManager, thumbnailCache),
  128. displayFullThumb (false)
  129. {
  130. formatManager.registerBasicFormats();
  131. thumbnail.addChangeListener (this);
  132. }
  133. ~RecordingThumbnail()
  134. {
  135. thumbnail.removeChangeListener (this);
  136. }
  137. AudioThumbnail& getAudioThumbnail() { return thumbnail; }
  138. void setDisplayFullThumbnail (bool displayFull)
  139. {
  140. displayFullThumb = displayFull;
  141. repaint();
  142. }
  143. void paint (Graphics& g) override
  144. {
  145. g.fillAll (Colours::darkgrey);
  146. g.setColour (Colours::lightgrey);
  147. if (thumbnail.getTotalLength() > 0.0)
  148. {
  149. const double endTime = displayFullThumb ? thumbnail.getTotalLength()
  150. : jmax (30.0, thumbnail.getTotalLength());
  151. Rectangle<int> thumbArea (getLocalBounds());
  152. thumbnail.drawChannels (g, thumbArea.reduced (2), 0.0, endTime, 1.0f);
  153. }
  154. else
  155. {
  156. g.setFont (14.0f);
  157. g.drawFittedText ("(No file recorded)", getLocalBounds(), Justification::centred, 2);
  158. }
  159. }
  160. private:
  161. AudioFormatManager formatManager;
  162. AudioThumbnailCache thumbnailCache;
  163. AudioThumbnail thumbnail;
  164. bool displayFullThumb;
  165. void changeListenerCallback (ChangeBroadcaster* source) override
  166. {
  167. if (source == &thumbnail)
  168. repaint();
  169. }
  170. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecordingThumbnail)
  171. };
  172. //==============================================================================
  173. class AudioRecordingDemo : public Component,
  174. private Button::Listener
  175. {
  176. public:
  177. AudioRecordingDemo()
  178. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  179. recorder (recordingThumbnail.getAudioThumbnail())
  180. {
  181. setOpaque (true);
  182. addAndMakeVisible (liveAudioScroller);
  183. addAndMakeVisible (explanationLabel);
  184. explanationLabel.setText ("This page demonstrates how to record a wave file from the live audio input..\n\nPressing record will start recording a file in your \"Documents\" folder.", dontSendNotification);
  185. explanationLabel.setFont (Font (15.00f, Font::plain));
  186. explanationLabel.setJustificationType (Justification::topLeft);
  187. explanationLabel.setEditable (false, false, false);
  188. explanationLabel.setColour (TextEditor::textColourId, Colours::black);
  189. explanationLabel.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  190. addAndMakeVisible (recordButton);
  191. recordButton.setButtonText ("Record");
  192. recordButton.addListener (this);
  193. recordButton.setColour (TextButton::buttonColourId, Colour (0xffff5c5c));
  194. recordButton.setColour (TextButton::textColourOnId, Colours::black);
  195. addAndMakeVisible (recordingThumbnail);
  196. deviceManager.addAudioCallback (&liveAudioScroller);
  197. deviceManager.addAudioCallback (&recorder);
  198. }
  199. ~AudioRecordingDemo()
  200. {
  201. deviceManager.removeAudioCallback (&recorder);
  202. deviceManager.removeAudioCallback (&liveAudioScroller);
  203. }
  204. void paint (Graphics& g) override
  205. {
  206. fillTiledBackground (g);
  207. }
  208. void resized() override
  209. {
  210. Rectangle<int> area (getLocalBounds());
  211. liveAudioScroller.setBounds (area.removeFromTop (80).reduced (8));
  212. recordingThumbnail.setBounds (area.removeFromTop (80).reduced (8));
  213. recordButton.setBounds (area.removeFromTop (36).removeFromLeft (140).reduced (8));
  214. explanationLabel.setBounds (area.reduced (8));
  215. }
  216. private:
  217. AudioDeviceManager& deviceManager;
  218. LiveScrollingAudioDisplay liveAudioScroller;
  219. RecordingThumbnail recordingThumbnail;
  220. AudioRecorder recorder;
  221. Label explanationLabel;
  222. TextButton recordButton;
  223. void startRecording()
  224. {
  225. const File file (File::getSpecialLocation (File::userDocumentsDirectory)
  226. .getNonexistentChildFile ("Juce Demo Audio Recording", ".wav"));
  227. recorder.startRecording (file);
  228. recordButton.setButtonText ("Stop");
  229. recordingThumbnail.setDisplayFullThumbnail (false);
  230. }
  231. void stopRecording()
  232. {
  233. recorder.stop();
  234. recordButton.setButtonText ("Record");
  235. recordingThumbnail.setDisplayFullThumbnail (true);
  236. }
  237. void buttonClicked (Button* button) override
  238. {
  239. if (button == &recordButton)
  240. {
  241. if (recorder.isRecording())
  242. stopRecording();
  243. else
  244. startRecording();
  245. }
  246. }
  247. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioRecordingDemo)
  248. };
  249. // This static object will register this demo type in a global list of demos..
  250. static JuceDemoType<AudioRecordingDemo> demo ("31 Audio: Recording");