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