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.

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