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.

419 lines
13KB

  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. namespace juce
  20. {
  21. namespace CDBurnerHelpers
  22. {
  23. IDiscRecorder* enumCDBurners (StringArray* list, int indexToOpen, IDiscMaster** master)
  24. {
  25. CoInitialize (0);
  26. IDiscMaster* dm;
  27. IDiscRecorder* result = nullptr;
  28. if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj, 0,
  29. CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
  30. IID_IDiscMaster,
  31. (void**) &dm)))
  32. {
  33. if (SUCCEEDED (dm->Open()))
  34. {
  35. IEnumDiscRecorders* drEnum = nullptr;
  36. if (SUCCEEDED (dm->EnumDiscRecorders (&drEnum)))
  37. {
  38. IDiscRecorder* dr = nullptr;
  39. DWORD dummy;
  40. int index = 0;
  41. while (drEnum->Next (1, &dr, &dummy) == S_OK)
  42. {
  43. if (indexToOpen == index)
  44. {
  45. result = dr;
  46. break;
  47. }
  48. else if (list != nullptr)
  49. {
  50. BSTR path;
  51. if (SUCCEEDED (dr->GetPath (&path)))
  52. list->add ((const WCHAR*) path);
  53. }
  54. ++index;
  55. dr->Release();
  56. }
  57. drEnum->Release();
  58. }
  59. if (master == 0)
  60. dm->Close();
  61. }
  62. if (master != nullptr)
  63. *master = dm;
  64. else
  65. dm->Release();
  66. }
  67. return result;
  68. }
  69. }
  70. //==============================================================================
  71. class AudioCDBurner::Pimpl : public ComBaseClassHelper <IDiscMasterProgressEvents>,
  72. public Timer
  73. {
  74. public:
  75. Pimpl (AudioCDBurner& owner_, IDiscMaster* discMaster_, IDiscRecorder* discRecorder_)
  76. : owner (owner_), discMaster (discMaster_), discRecorder (discRecorder_), redbook (0),
  77. listener (0), progress (0), shouldCancel (false)
  78. {
  79. HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook);
  80. jassert (SUCCEEDED (hr));
  81. hr = discMaster->SetActiveDiscRecorder (discRecorder);
  82. //jassert (SUCCEEDED (hr));
  83. lastState = getDiskState();
  84. startTimer (2000);
  85. }
  86. ~Pimpl() {}
  87. void releaseObjects()
  88. {
  89. discRecorder->Close();
  90. if (redbook != nullptr)
  91. redbook->Release();
  92. discRecorder->Release();
  93. discMaster->Release();
  94. Release();
  95. }
  96. JUCE_COMRESULT QueryCancel (boolean* pbCancel)
  97. {
  98. if (listener != nullptr && ! shouldCancel)
  99. shouldCancel = listener->audioCDBurnProgress (progress);
  100. *pbCancel = shouldCancel;
  101. return S_OK;
  102. }
  103. JUCE_COMRESULT NotifyBlockProgress (long nCompleted, long nTotal)
  104. {
  105. progress = nCompleted / (float) nTotal;
  106. shouldCancel = listener != nullptr && listener->audioCDBurnProgress (progress);
  107. return E_NOTIMPL;
  108. }
  109. JUCE_COMRESULT NotifyPnPActivity (void) { return E_NOTIMPL; }
  110. JUCE_COMRESULT NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/) { return E_NOTIMPL; }
  111. JUCE_COMRESULT NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/) { return E_NOTIMPL; }
  112. JUCE_COMRESULT NotifyPreparingBurn (long /*nEstimatedSeconds*/) { return E_NOTIMPL; }
  113. JUCE_COMRESULT NotifyClosingDisc (long /*nEstimatedSeconds*/) { return E_NOTIMPL; }
  114. JUCE_COMRESULT NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; }
  115. JUCE_COMRESULT NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; }
  116. class ScopedDiscOpener
  117. {
  118. public:
  119. ScopedDiscOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); }
  120. ~ScopedDiscOpener() { pimpl.discRecorder->Close(); }
  121. private:
  122. Pimpl& pimpl;
  123. JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener)
  124. };
  125. DiskState getDiskState()
  126. {
  127. const ScopedDiscOpener opener (*this);
  128. long type, flags;
  129. HRESULT hr = discRecorder->QueryMediaType (&type, &flags);
  130. if (FAILED (hr))
  131. return unknown;
  132. if (type != 0 && (flags & MEDIA_WRITABLE) != 0)
  133. return writableDiskPresent;
  134. if (type == 0)
  135. return noDisc;
  136. return readOnlyDiskPresent;
  137. }
  138. int getIntProperty (const LPOLESTR name, const int defaultReturn) const
  139. {
  140. ComSmartPtr<IPropertyStorage> prop;
  141. if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
  142. return defaultReturn;
  143. PROPSPEC iPropSpec;
  144. iPropSpec.ulKind = PRSPEC_LPWSTR;
  145. iPropSpec.lpwstr = name;
  146. PROPVARIANT iPropVariant;
  147. return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))
  148. ? defaultReturn : (int) iPropVariant.lVal;
  149. }
  150. bool setIntProperty (const LPOLESTR name, const int value) const
  151. {
  152. ComSmartPtr<IPropertyStorage> prop;
  153. if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
  154. return false;
  155. PROPSPEC iPropSpec;
  156. iPropSpec.ulKind = PRSPEC_LPWSTR;
  157. iPropSpec.lpwstr = name;
  158. PROPVARIANT iPropVariant;
  159. if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)))
  160. return false;
  161. iPropVariant.lVal = (long) value;
  162. return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt))
  163. && SUCCEEDED (discRecorder->SetRecorderProperties (prop));
  164. }
  165. void timerCallback() override
  166. {
  167. const DiskState state = getDiskState();
  168. if (state != lastState)
  169. {
  170. lastState = state;
  171. owner.sendChangeMessage();
  172. }
  173. }
  174. AudioCDBurner& owner;
  175. DiskState lastState;
  176. IDiscMaster* discMaster;
  177. IDiscRecorder* discRecorder;
  178. IRedbookDiscMaster* redbook;
  179. AudioCDBurner::BurnProgressListener* listener;
  180. float progress;
  181. bool shouldCancel;
  182. };
  183. //==============================================================================
  184. AudioCDBurner::AudioCDBurner (const int deviceIndex)
  185. {
  186. IDiscMaster* discMaster = nullptr;
  187. IDiscRecorder* discRecorder = CDBurnerHelpers::enumCDBurners (0, deviceIndex, &discMaster);
  188. if (discRecorder != nullptr)
  189. pimpl.reset (new Pimpl (*this, discMaster, discRecorder));
  190. }
  191. AudioCDBurner::~AudioCDBurner()
  192. {
  193. if (pimpl != nullptr)
  194. pimpl.release()->releaseObjects();
  195. }
  196. StringArray AudioCDBurner::findAvailableDevices()
  197. {
  198. StringArray devs;
  199. CDBurnerHelpers::enumCDBurners (&devs, -1, 0);
  200. return devs;
  201. }
  202. AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
  203. {
  204. std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
  205. if (b->pimpl == 0)
  206. b = nullptr;
  207. return b.release();
  208. }
  209. AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
  210. {
  211. return pimpl->getDiskState();
  212. }
  213. bool AudioCDBurner::isDiskPresent() const
  214. {
  215. return getDiskState() == writableDiskPresent;
  216. }
  217. bool AudioCDBurner::openTray()
  218. {
  219. const Pimpl::ScopedDiscOpener opener (*pimpl);
  220. return SUCCEEDED (pimpl->discRecorder->Eject());
  221. }
  222. AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
  223. {
  224. const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
  225. DiskState oldState = getDiskState();
  226. DiskState newState = oldState;
  227. while (newState == oldState && Time::currentTimeMillis() < timeout)
  228. {
  229. newState = getDiskState();
  230. Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis())));
  231. }
  232. return newState;
  233. }
  234. Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
  235. {
  236. Array<int> results;
  237. const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1);
  238. const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 };
  239. for (int i = 0; i < numElementsInArray (speeds); ++i)
  240. if (speeds[i] <= maxSpeed)
  241. results.add (speeds[i]);
  242. results.addIfNotAlreadyThere (maxSpeed);
  243. return results;
  244. }
  245. bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
  246. {
  247. if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0)
  248. return false;
  249. pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0);
  250. return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0;
  251. }
  252. int AudioCDBurner::getNumAvailableAudioBlocks() const
  253. {
  254. long blocksFree = 0;
  255. pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree);
  256. return blocksFree;
  257. }
  258. String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards,
  259. bool performFakeBurnForTesting, int writeSpeed)
  260. {
  261. pimpl->setIntProperty (L"WriteSpeed", writeSpeed > 0 ? writeSpeed : -1);
  262. pimpl->listener = listener;
  263. pimpl->progress = 0;
  264. pimpl->shouldCancel = false;
  265. UINT_PTR cookie;
  266. HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl.get(), &cookie);
  267. hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting,
  268. ejectDiscAfterwards);
  269. String error;
  270. if (hr != S_OK)
  271. {
  272. const char* e = "Couldn't open or write to the CD device";
  273. if (hr == IMAPI_E_USERABORT)
  274. e = "User cancelled the write operation";
  275. else if (hr == IMAPI_E_MEDIUM_NOTPRESENT || hr == IMAPI_E_TRACKOPEN)
  276. e = "No Disk present";
  277. error = e;
  278. }
  279. pimpl->discMaster->ProgressUnadvise (cookie);
  280. pimpl->listener = 0;
  281. return error;
  282. }
  283. bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples)
  284. {
  285. if (audioSource == 0)
  286. return false;
  287. std::unique_ptr<AudioSource> source (audioSource);
  288. long bytesPerBlock;
  289. HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock);
  290. const int samplesPerBlock = bytesPerBlock / 4;
  291. bool ok = true;
  292. hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4));
  293. HeapBlock<byte> buffer (bytesPerBlock);
  294. AudioBuffer<float> sourceBuffer (2, samplesPerBlock);
  295. int samplesDone = 0;
  296. source->prepareToPlay (samplesPerBlock, 44100.0);
  297. while (ok)
  298. {
  299. {
  300. AudioSourceChannelInfo info (&sourceBuffer, 0, samplesPerBlock);
  301. sourceBuffer.clear();
  302. source->getNextAudioBlock (info);
  303. }
  304. buffer.clear (bytesPerBlock);
  305. typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian,
  306. AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
  307. typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian,
  308. AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
  309. CDSampleFormat left (buffer, 2);
  310. left.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (0)), samplesPerBlock);
  311. CDSampleFormat right (buffer + 2, 2);
  312. right.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (1)), samplesPerBlock);
  313. hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock);
  314. if (FAILED (hr))
  315. ok = false;
  316. samplesDone += samplesPerBlock;
  317. if (samplesDone >= numSamples)
  318. break;
  319. }
  320. hr = pimpl->redbook->CloseAudioTrack();
  321. return ok && hr == S_OK;
  322. }
  323. } // namespace juce