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.

561 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. const int kilobytesPerSecond1x = 176;
  19. END_JUCE_NAMESPACE
  20. #define OpenDiskDevice MakeObjCClassName(OpenDiskDevice)
  21. @interface OpenDiskDevice : NSObject
  22. {
  23. @public
  24. DRDevice* device;
  25. NSMutableArray* tracks;
  26. bool underrunProtection;
  27. }
  28. - (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device;
  29. - (void) dealloc;
  30. - (void) addSourceTrack: (juce::AudioSource*) source numSamples: (int) numSamples_;
  31. - (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error
  32. ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed;
  33. @end
  34. //==============================================================================
  35. #define AudioTrackProducer MakeObjCClassName(AudioTrackProducer)
  36. @interface AudioTrackProducer : NSObject
  37. {
  38. juce::AudioSource* source;
  39. int readPosition, lengthInFrames;
  40. }
  41. - (AudioTrackProducer*) init: (int) lengthInFrames;
  42. - (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source numSamples: (int) lengthInSamples;
  43. - (void) dealloc;
  44. - (void) setupTrackProperties: (DRTrack*) track;
  45. - (void) cleanupTrackAfterBurn: (DRTrack*) track;
  46. - (BOOL) cleanupTrackAfterVerification:(DRTrack*)track;
  47. - (uint64_t) estimateLengthOfTrack:(DRTrack*)track;
  48. - (BOOL) prepareTrack:(DRTrack*)track forBurn:(DRBurn*)burn
  49. toMedia:(NSDictionary*)mediaInfo;
  50. - (BOOL) prepareTrackForVerification:(DRTrack*)track;
  51. - (uint32_t) produceDataForTrack:(DRTrack*)track intoBuffer:(char*)buffer
  52. length:(uint32_t)bufferLength atAddress:(uint64_t)address
  53. blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags;
  54. - (uint32_t) producePreGapForTrack:(DRTrack*)track
  55. intoBuffer:(char*)buffer length:(uint32_t)bufferLength
  56. atAddress:(uint64_t)address blockSize:(uint32_t)blockSize
  57. ioFlags:(uint32_t*)flags;
  58. - (BOOL) verifyDataForTrack:(DRTrack*)track inBuffer:(const char*)buffer
  59. length:(uint32_t)bufferLength atAddress:(uint64_t)address
  60. blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags;
  61. - (uint32_t) producePreGapForTrack:(DRTrack*)track
  62. intoBuffer:(char*)buffer length:(uint32_t)bufferLength
  63. atAddress:(uint64_t)address blockSize:(uint32_t)blockSize
  64. ioFlags:(uint32_t*)flags;
  65. @end
  66. //==============================================================================
  67. @implementation OpenDiskDevice
  68. - (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_
  69. {
  70. [super init];
  71. device = device_;
  72. tracks = [[NSMutableArray alloc] init];
  73. underrunProtection = true;
  74. return self;
  75. }
  76. - (void) dealloc
  77. {
  78. [tracks release];
  79. [super dealloc];
  80. }
  81. - (void) addSourceTrack: (juce::AudioSource*) source_ numSamples: (int) numSamples_
  82. {
  83. AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_];
  84. DRTrack* t = [[DRTrack alloc] initWithProducer: p];
  85. [p setupTrackProperties: t];
  86. [tracks addObject: t];
  87. [t release];
  88. [p release];
  89. }
  90. - (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error
  91. ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed
  92. {
  93. DRBurn* burn = [DRBurn burnForDevice: device];
  94. if (! [device acquireExclusiveAccess])
  95. {
  96. *error = "Couldn't open or write to the CD device";
  97. return;
  98. }
  99. [device acquireMediaReservation];
  100. NSMutableDictionary* d = [[burn properties] mutableCopy];
  101. [d autorelease];
  102. [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey];
  103. [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey];
  104. [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey];
  105. if (burnSpeed > 0)
  106. [d setObject: [NSNumber numberWithFloat: burnSpeed * juce::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey];
  107. if (! underrunProtection)
  108. [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey];
  109. [burn setProperties: d];
  110. [burn writeLayout: tracks];
  111. for (;;)
  112. {
  113. juce::Thread::sleep (300);
  114. float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue];
  115. if (listener != nullptr && listener->audioCDBurnProgress (progress))
  116. {
  117. [burn abort];
  118. *error = "User cancelled the write operation";
  119. break;
  120. }
  121. if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed])
  122. {
  123. *error = "Write operation failed";
  124. break;
  125. }
  126. else if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
  127. {
  128. break;
  129. }
  130. NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey]
  131. objectForKey: DRErrorStatusErrorStringKey];
  132. if ([err length] > 0)
  133. {
  134. *error = juce::CharPointer_UTF8 ([err UTF8String]);
  135. break;
  136. }
  137. }
  138. [device releaseMediaReservation];
  139. [device releaseExclusiveAccess];
  140. }
  141. @end
  142. //==============================================================================
  143. @implementation AudioTrackProducer
  144. - (AudioTrackProducer*) init: (int) lengthInFrames_
  145. {
  146. lengthInFrames = lengthInFrames_;
  147. readPosition = 0;
  148. return self;
  149. }
  150. - (void) setupTrackProperties: (DRTrack*) track
  151. {
  152. NSMutableDictionary* p = [[track properties] mutableCopy];
  153. [p setObject:[DRMSF msfWithFrames: lengthInFrames] forKey: DRTrackLengthKey];
  154. [p setObject:[NSNumber numberWithUnsignedShort:2352] forKey: DRBlockSizeKey];
  155. [p setObject:[NSNumber numberWithInt:0] forKey: DRDataFormKey];
  156. [p setObject:[NSNumber numberWithInt:0] forKey: DRBlockTypeKey];
  157. [p setObject:[NSNumber numberWithInt:0] forKey: DRTrackModeKey];
  158. [p setObject:[NSNumber numberWithInt:0] forKey: DRSessionFormatKey];
  159. [track setProperties: p];
  160. [p release];
  161. }
  162. - (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source_ numSamples: (int) lengthInSamples
  163. {
  164. AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588];
  165. if (s != nil)
  166. s->source = source_;
  167. return s;
  168. }
  169. - (void) dealloc
  170. {
  171. if (source != nullptr)
  172. {
  173. source->releaseResources();
  174. delete source;
  175. }
  176. [super dealloc];
  177. }
  178. - (void) cleanupTrackAfterBurn: (DRTrack*) track
  179. {
  180. (void) track;
  181. }
  182. - (BOOL) cleanupTrackAfterVerification: (DRTrack*) track
  183. {
  184. (void) track;
  185. return true;
  186. }
  187. - (uint64_t) estimateLengthOfTrack: (DRTrack*) track
  188. {
  189. (void) track;
  190. return lengthInFrames;
  191. }
  192. - (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn
  193. toMedia: (NSDictionary*) mediaInfo
  194. {
  195. (void) track; (void) burn; (void) mediaInfo;
  196. if (source != nullptr)
  197. source->prepareToPlay (44100 / 75, 44100);
  198. readPosition = 0;
  199. return true;
  200. }
  201. - (BOOL) prepareTrackForVerification: (DRTrack*) track
  202. {
  203. (void) track;
  204. if (source != nullptr)
  205. source->prepareToPlay (44100 / 75, 44100);
  206. return true;
  207. }
  208. - (uint32_t) produceDataForTrack: (DRTrack*) track intoBuffer: (char*) buffer
  209. length: (uint32_t) bufferLength atAddress: (uint64_t) address
  210. blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags
  211. {
  212. (void) track; (void) address; (void) blockSize; (void) flags;
  213. if (source != nullptr)
  214. {
  215. const int numSamples = juce::jmin ((int) bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition);
  216. if (numSamples > 0)
  217. {
  218. juce::AudioSampleBuffer tempBuffer (2, numSamples);
  219. juce::AudioSourceChannelInfo info;
  220. info.buffer = &tempBuffer;
  221. info.startSample = 0;
  222. info.numSamples = numSamples;
  223. source->getNextAudioBlock (info);
  224. typedef juce::AudioData::Pointer <juce::AudioData::Int16,
  225. juce::AudioData::LittleEndian,
  226. juce::AudioData::Interleaved,
  227. juce::AudioData::NonConst> CDSampleFormat;
  228. typedef juce::AudioData::Pointer <juce::AudioData::Float32,
  229. juce::AudioData::NativeEndian,
  230. juce::AudioData::NonInterleaved,
  231. juce::AudioData::Const> SourceSampleFormat;
  232. CDSampleFormat left (buffer, 2);
  233. left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples);
  234. CDSampleFormat right (buffer + 2, 2);
  235. right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples);
  236. readPosition += numSamples;
  237. }
  238. return numSamples * 4;
  239. }
  240. return 0;
  241. }
  242. - (uint32_t) producePreGapForTrack: (DRTrack*) track
  243. intoBuffer: (char*) buffer length: (uint32_t) bufferLength
  244. atAddress: (uint64_t) address blockSize: (uint32_t) blockSize
  245. ioFlags: (uint32_t*) flags
  246. {
  247. (void) track; (void) address; (void) blockSize; (void) flags;
  248. zeromem (buffer, bufferLength);
  249. return bufferLength;
  250. }
  251. - (BOOL) verifyDataForTrack: (DRTrack*) track inBuffer: (const char*) buffer
  252. length: (uint32_t) bufferLength atAddress: (uint64_t) address
  253. blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags
  254. {
  255. (void) track; (void) buffer; (void) bufferLength; (void) address; (void) blockSize; (void) flags;
  256. return true;
  257. }
  258. @end
  259. BEGIN_JUCE_NAMESPACE
  260. //==============================================================================
  261. class AudioCDBurner::Pimpl : public Timer
  262. {
  263. public:
  264. Pimpl (AudioCDBurner& owner_, const int deviceIndex)
  265. : device (0), owner (owner_)
  266. {
  267. DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex];
  268. if (dev != nil)
  269. {
  270. device = [[OpenDiskDevice alloc] initWithDRDevice: dev];
  271. lastState = getDiskState();
  272. startTimer (1000);
  273. }
  274. }
  275. ~Pimpl()
  276. {
  277. stopTimer();
  278. [device release];
  279. }
  280. void timerCallback()
  281. {
  282. const DiskState state = getDiskState();
  283. if (state != lastState)
  284. {
  285. lastState = state;
  286. owner.sendChangeMessage();
  287. }
  288. }
  289. DiskState getDiskState() const
  290. {
  291. if ([device->device isValid])
  292. {
  293. NSDictionary* status = [device->device status];
  294. NSString* state = [status objectForKey: DRDeviceMediaStateKey];
  295. if ([state isEqualTo: DRDeviceMediaStateNone])
  296. {
  297. if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue])
  298. return trayOpen;
  299. return noDisc;
  300. }
  301. if ([state isEqualTo: DRDeviceMediaStateMediaPresent])
  302. {
  303. if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0)
  304. return writableDiskPresent;
  305. else
  306. return readOnlyDiskPresent;
  307. }
  308. }
  309. return unknown;
  310. }
  311. bool openTray() { return [device->device isValid] && [device->device ejectMedia]; }
  312. Array<int> getAvailableWriteSpeeds() const
  313. {
  314. Array<int> results;
  315. if ([device->device isValid])
  316. {
  317. NSArray* speeds = [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey];
  318. for (unsigned int i = 0; i < [speeds count]; ++i)
  319. {
  320. const int kbPerSec = [[speeds objectAtIndex: i] intValue];
  321. results.add (kbPerSec / kilobytesPerSecond1x);
  322. }
  323. }
  324. return results;
  325. }
  326. bool setBufferUnderrunProtection (const bool shouldBeEnabled)
  327. {
  328. if ([device->device isValid])
  329. {
  330. device->underrunProtection = shouldBeEnabled;
  331. return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue];
  332. }
  333. return false;
  334. }
  335. int getNumAvailableAudioBlocks() const
  336. {
  337. return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey]
  338. objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
  339. }
  340. OpenDiskDevice* device;
  341. private:
  342. DiskState lastState;
  343. AudioCDBurner& owner;
  344. };
  345. //==============================================================================
  346. AudioCDBurner::AudioCDBurner (const int deviceIndex)
  347. {
  348. pimpl = new Pimpl (*this, deviceIndex);
  349. }
  350. AudioCDBurner::~AudioCDBurner()
  351. {
  352. }
  353. AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
  354. {
  355. ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex));
  356. if (b->pimpl->device == nil)
  357. b = 0;
  358. return b.release();
  359. }
  360. namespace
  361. {
  362. NSArray* findDiskBurnerDevices()
  363. {
  364. NSMutableArray* results = [NSMutableArray array];
  365. NSArray* devs = [DRDevice devices];
  366. for (int i = 0; i < [devs count]; ++i)
  367. {
  368. NSDictionary* dic = [[devs objectAtIndex: i] info];
  369. NSString* name = [dic valueForKey: DRDeviceProductNameKey];
  370. if (name != nil)
  371. [results addObject: name];
  372. }
  373. return results;
  374. }
  375. }
  376. StringArray AudioCDBurner::findAvailableDevices()
  377. {
  378. NSArray* names = findDiskBurnerDevices();
  379. StringArray s;
  380. for (unsigned int i = 0; i < [names count]; ++i)
  381. s.add (CharPointer_UTF8 ([[names objectAtIndex: i] UTF8String]));
  382. return s;
  383. }
  384. AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
  385. {
  386. return pimpl->getDiskState();
  387. }
  388. bool AudioCDBurner::isDiskPresent() const
  389. {
  390. return getDiskState() == writableDiskPresent;
  391. }
  392. bool AudioCDBurner::openTray()
  393. {
  394. return pimpl->openTray();
  395. }
  396. AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
  397. {
  398. const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
  399. DiskState oldState = getDiskState();
  400. DiskState newState = oldState;
  401. while (newState == oldState && Time::currentTimeMillis() < timeout)
  402. {
  403. newState = getDiskState();
  404. Thread::sleep (100);
  405. }
  406. return newState;
  407. }
  408. Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
  409. {
  410. return pimpl->getAvailableWriteSpeeds();
  411. }
  412. bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
  413. {
  414. return pimpl->setBufferUnderrunProtection (shouldBeEnabled);
  415. }
  416. int AudioCDBurner::getNumAvailableAudioBlocks() const
  417. {
  418. return pimpl->getNumAvailableAudioBlocks();
  419. }
  420. bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps)
  421. {
  422. if ([pimpl->device->device isValid])
  423. {
  424. [pimpl->device addSourceTrack: source numSamples: numSamps];
  425. return true;
  426. }
  427. return false;
  428. }
  429. String AudioCDBurner::burn (juce::AudioCDBurner::BurnProgressListener* listener,
  430. bool ejectDiscAfterwards,
  431. bool performFakeBurnForTesting,
  432. int writeSpeed)
  433. {
  434. String error ("Couldn't open or write to the CD device");
  435. if ([pimpl->device->device isValid])
  436. {
  437. error = String::empty;
  438. [pimpl->device burn: listener
  439. errorString: &error
  440. ejectAfterwards: ejectDiscAfterwards
  441. isFake: performFakeBurnForTesting
  442. speed: writeSpeed];
  443. }
  444. return error;
  445. }