Audio plugin host https://kx.studio/carla
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.

412 lines
13KB

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