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.

411 lines
13KB

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