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.

470 lines
16KB

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