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.

460 lines
15KB

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