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.

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