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.

462 lines
15KB

  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. const int kilobytesPerSecond1x = 176;
  21. struct AudioTrackProducerClass : public ObjCClass<NSObject>
  22. {
  23. AudioTrackProducerClass() : ObjCClass<NSObject> ("JUCEAudioTrackProducer_")
  24. {
  25. addIvar<AudioSourceHolder*> ("source");
  26. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  27. addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder);
  28. addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), produceDataForTrack);
  29. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  30. addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn);
  31. addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification);
  32. addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack);
  33. addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack);
  34. addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification);
  35. addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
  36. produceDataForTrack);
  37. addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
  38. produceDataForTrack);
  39. registerClass();
  40. }
  41. struct AudioSourceHolder
  42. {
  43. AudioSourceHolder (AudioSource* s, int numFrames)
  44. : source (s), readPosition (0), lengthInFrames (numFrames)
  45. {
  46. }
  47. ~AudioSourceHolder()
  48. {
  49. if (source != nullptr)
  50. source->releaseResources();
  51. }
  52. std::unique_ptr<AudioSource> source;
  53. int readPosition, lengthInFrames;
  54. };
  55. private:
  56. static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source)
  57. {
  58. self = sendSuperclassMessage<id> (self, @selector (init));
  59. object_setInstanceVariable (self, "source", source);
  60. return self;
  61. }
  62. static AudioSourceHolder* getSource (id self)
  63. {
  64. return getIvar<AudioSourceHolder*> (self, "source");
  65. }
  66. static void dealloc (id self, SEL)
  67. {
  68. delete getSource (self);
  69. sendSuperclassMessage<void> (self, @selector (dealloc));
  70. }
  71. static void cleanupTrackAfterBurn (id, SEL, DRTrack*) {}
  72. static BOOL cleanupTrackAfterVerification (id, SEL, DRTrack*) { return true; }
  73. static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*)
  74. {
  75. return static_cast<uint64_t> (getSource (self)->lengthInFrames);
  76. }
  77. static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*)
  78. {
  79. if (AudioSourceHolder* const source = getSource (self))
  80. {
  81. source->source->prepareToPlay (44100 / 75, 44100);
  82. source->readPosition = 0;
  83. }
  84. return true;
  85. }
  86. static BOOL prepareTrackForVerification (id self, SEL, DRTrack*)
  87. {
  88. if (AudioSourceHolder* const source = getSource (self))
  89. source->source->prepareToPlay (44100 / 75, 44100);
  90. return true;
  91. }
  92. static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer,
  93. uint32_t bufferLength, uint64_t /*address*/,
  94. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  95. {
  96. if (AudioSourceHolder* const source = getSource (self))
  97. {
  98. const int numSamples = jmin ((int) bufferLength / 4,
  99. (source->lengthInFrames * (44100 / 75)) - source->readPosition);
  100. if (numSamples > 0)
  101. {
  102. AudioBuffer<float> tempBuffer (2, numSamples);
  103. AudioSourceChannelInfo info (tempBuffer);
  104. source->source->getNextAudioBlock (info);
  105. AudioData::interleaveSamples (AudioData::NonInterleavedSource<AudioData::Float32, AudioData::NativeEndian> { tempBuffer.getArrayOfReadPointers(), 2 },
  106. AudioData::InterleavedDest<AudioData::Int16, AudioData::LittleEndian> { reinterpret_cast<uint16*> (buffer), 2 },
  107. numSamples);
  108. source->readPosition += numSamples;
  109. }
  110. return static_cast<uint32_t> (numSamples * 4);
  111. }
  112. return 0;
  113. }
  114. static uint32_t producePreGapForTrack (id, SEL, DRTrack*, char* buffer,
  115. uint32_t bufferLength, uint64_t /*address*/,
  116. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  117. {
  118. zeromem (buffer, bufferLength);
  119. return bufferLength;
  120. }
  121. static BOOL verifyDataForTrack (id, SEL, DRTrack*, const char*,
  122. uint32_t /*bufferLength*/, uint64_t /*address*/,
  123. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  124. {
  125. return true;
  126. }
  127. };
  128. struct OpenDiskDevice
  129. {
  130. OpenDiskDevice (DRDevice* d)
  131. : device (d),
  132. tracks ([[NSMutableArray alloc] init]),
  133. underrunProtection (true)
  134. {
  135. }
  136. ~OpenDiskDevice()
  137. {
  138. [tracks release];
  139. }
  140. void addSourceTrack (AudioSource* source, int numSamples)
  141. {
  142. if (source != nullptr)
  143. {
  144. const int numFrames = (numSamples + 587) / 588;
  145. static AudioTrackProducerClass cls;
  146. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  147. NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
  148. withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
  149. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  150. DRTrack* track = [[DRTrack alloc] initWithProducer: producer];
  151. {
  152. NSMutableDictionary* p = [[track properties] mutableCopy];
  153. [p setObject: [DRMSF msfWithFrames: static_cast<UInt32> (numFrames)] forKey: DRTrackLengthKey];
  154. [p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey];
  155. [p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey];
  156. [p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey];
  157. [p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey];
  158. [p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey];
  159. [track setProperties: p];
  160. [p release];
  161. }
  162. [tracks addObject: track];
  163. [track release];
  164. [producer release];
  165. }
  166. }
  167. String burn (AudioCDBurner::BurnProgressListener* listener,
  168. bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed)
  169. {
  170. DRBurn* burn = [DRBurn burnForDevice: device];
  171. if (! [device acquireExclusiveAccess])
  172. return "Couldn't open or write to the CD device";
  173. [device acquireMediaReservation];
  174. NSMutableDictionary* d = [[burn properties] mutableCopy];
  175. [d autorelease];
  176. [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey];
  177. [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey];
  178. [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey];
  179. if (burnSpeed > 0)
  180. [d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey];
  181. if (! underrunProtection)
  182. [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey];
  183. [burn setProperties: d];
  184. [burn writeLayout: tracks];
  185. for (;;)
  186. {
  187. Thread::sleep (300);
  188. float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue];
  189. if (listener != nullptr && listener->audioCDBurnProgress (progress))
  190. {
  191. [burn abort];
  192. return "User cancelled the write operation";
  193. }
  194. if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed])
  195. return "Write operation failed";
  196. if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
  197. break;
  198. NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey]
  199. objectForKey: DRErrorStatusErrorStringKey];
  200. if ([err length] > 0)
  201. return nsStringToJuce (err);
  202. }
  203. [device releaseMediaReservation];
  204. [device releaseExclusiveAccess];
  205. return {};
  206. }
  207. DRDevice* device;
  208. NSMutableArray* tracks;
  209. bool underrunProtection;
  210. };
  211. //==============================================================================
  212. class AudioCDBurner::Pimpl : private Timer
  213. {
  214. public:
  215. Pimpl (AudioCDBurner& b, int deviceIndex) : owner (b)
  216. {
  217. if (DRDevice* dev = [[DRDevice devices] objectAtIndex: static_cast<NSUInteger> (deviceIndex)])
  218. {
  219. device.reset (new OpenDiskDevice (dev));
  220. lastState = getDiskState();
  221. startTimer (1000);
  222. }
  223. }
  224. ~Pimpl() override
  225. {
  226. stopTimer();
  227. }
  228. DiskState getDiskState() const
  229. {
  230. if ([device->device isValid])
  231. {
  232. NSDictionary* status = [device->device status];
  233. NSString* state = [status objectForKey: DRDeviceMediaStateKey];
  234. if ([state isEqualTo: DRDeviceMediaStateNone])
  235. {
  236. if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue])
  237. return trayOpen;
  238. return noDisc;
  239. }
  240. if ([state isEqualTo: DRDeviceMediaStateMediaPresent])
  241. {
  242. if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0)
  243. return writableDiskPresent;
  244. return readOnlyDiskPresent;
  245. }
  246. }
  247. return unknown;
  248. }
  249. bool openTray() { return [device->device isValid] && [device->device ejectMedia]; }
  250. Array<int> getAvailableWriteSpeeds() const
  251. {
  252. Array<int> results;
  253. if ([device->device isValid])
  254. for (id kbPerSec in [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey])
  255. results.add ([kbPerSec intValue] / kilobytesPerSecond1x);
  256. return results;
  257. }
  258. bool setBufferUnderrunProtection (const bool shouldBeEnabled)
  259. {
  260. if ([device->device isValid])
  261. {
  262. device->underrunProtection = shouldBeEnabled;
  263. return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue];
  264. }
  265. return false;
  266. }
  267. int getNumAvailableAudioBlocks() const
  268. {
  269. return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey]
  270. objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
  271. }
  272. std::unique_ptr<OpenDiskDevice> device;
  273. private:
  274. void timerCallback() override
  275. {
  276. const DiskState state = getDiskState();
  277. if (state != lastState)
  278. {
  279. lastState = state;
  280. owner.sendChangeMessage();
  281. }
  282. }
  283. DiskState lastState;
  284. AudioCDBurner& owner;
  285. };
  286. //==============================================================================
  287. AudioCDBurner::AudioCDBurner (const int deviceIndex)
  288. {
  289. pimpl.reset (new Pimpl (*this, deviceIndex));
  290. }
  291. AudioCDBurner::~AudioCDBurner()
  292. {
  293. }
  294. AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
  295. {
  296. std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
  297. if (b->pimpl->device == nil)
  298. b = nullptr;
  299. return b.release();
  300. }
  301. StringArray AudioCDBurner::findAvailableDevices()
  302. {
  303. StringArray s;
  304. for (NSDictionary* dic in [DRDevice devices])
  305. if (NSString* name = [dic valueForKey: DRDeviceProductNameKey])
  306. s.add (nsStringToJuce (name));
  307. return s;
  308. }
  309. AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
  310. {
  311. return pimpl->getDiskState();
  312. }
  313. bool AudioCDBurner::isDiskPresent() const
  314. {
  315. return getDiskState() == writableDiskPresent;
  316. }
  317. bool AudioCDBurner::openTray()
  318. {
  319. return pimpl->openTray();
  320. }
  321. AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
  322. {
  323. const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
  324. DiskState oldState = getDiskState();
  325. DiskState newState = oldState;
  326. while (newState == oldState && Time::currentTimeMillis() < timeout)
  327. {
  328. newState = getDiskState();
  329. Thread::sleep (100);
  330. }
  331. return newState;
  332. }
  333. Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
  334. {
  335. return pimpl->getAvailableWriteSpeeds();
  336. }
  337. bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
  338. {
  339. return pimpl->setBufferUnderrunProtection (shouldBeEnabled);
  340. }
  341. int AudioCDBurner::getNumAvailableAudioBlocks() const
  342. {
  343. return pimpl->getNumAvailableAudioBlocks();
  344. }
  345. bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps)
  346. {
  347. if ([pimpl->device->device isValid])
  348. {
  349. pimpl->device->addSourceTrack (source, numSamps);
  350. return true;
  351. }
  352. return false;
  353. }
  354. String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener,
  355. bool ejectDiscAfterwards,
  356. bool performFakeBurnForTesting,
  357. int writeSpeed)
  358. {
  359. if ([pimpl->device->device isValid])
  360. return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed);
  361. return "Couldn't open or write to the CD device";
  362. }
  363. }