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.

306 lines
11KB

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