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.

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