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.

467 lines
16KB

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