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.

304 lines
11KB

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