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.

499 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. struct AudioTrackProducerClass : public ObjCClass <NSObject>
  20. {
  21. AudioTrackProducerClass() : ObjCClass <NSObject> ("JUCEAudioTrackProducer_")
  22. {
  23. addIvar<AudioSourceHolder*> ("source");
  24. addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v");
  25. addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@");
  26. addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@");
  27. addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@");
  28. addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@");
  29. addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@");
  30. addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
  31. produceDataForTrack, "I@:@^cIQI^I");
  32. addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
  33. produceDataForTrack, "I@:@^cIQI^I");
  34. addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
  35. produceDataForTrack, "I@:@^cIQI^I");
  36. registerClass();
  37. }
  38. struct AudioSourceHolder
  39. {
  40. AudioSourceHolder (AudioSource* source_, int numFrames)
  41. : source (source_), readPosition (0), lengthInFrames (numFrames)
  42. {
  43. }
  44. ~AudioSourceHolder()
  45. {
  46. if (source != nullptr)
  47. source->releaseResources();
  48. }
  49. ScopedPointer<AudioSource> source;
  50. int readPosition, lengthInFrames;
  51. };
  52. private:
  53. static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source)
  54. {
  55. self = sendSuperclassMessage (self, @selector (init));
  56. object_setInstanceVariable (self, "source", source);
  57. return self;
  58. }
  59. static AudioSourceHolder* getSource (id self)
  60. {
  61. return getIvar<AudioSourceHolder*> (self, "source");
  62. }
  63. static void dealloc (id self, SEL)
  64. {
  65. delete getSource (self);
  66. sendSuperclassMessage (self, @selector (dealloc));
  67. }
  68. static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {}
  69. static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; }
  70. static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*)
  71. {
  72. return getSource (self)->lengthInFrames;
  73. }
  74. static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*)
  75. {
  76. AudioSourceHolder* const source = getSource (self);
  77. if (source != nullptr)
  78. {
  79. source->source->prepareToPlay (44100 / 75, 44100);
  80. source->readPosition = 0;
  81. }
  82. return true;
  83. }
  84. static BOOL prepareTrackForVerification (id self, SEL, DRTrack*)
  85. {
  86. AudioSourceHolder* const source = getSource (self);
  87. if (source != nullptr)
  88. source->source->prepareToPlay (44100 / 75, 44100);
  89. return true;
  90. }
  91. static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer,
  92. uint32_t bufferLength, uint64_t /*address*/,
  93. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  94. {
  95. AudioSourceHolder* const source = getSource (self);
  96. if (source != nullptr)
  97. {
  98. const int numSamples = jmin ((int) bufferLength / 4,
  99. (source->lengthInFrames * (44100 / 75)) - source->readPosition);
  100. if (numSamples > 0)
  101. {
  102. AudioSampleBuffer tempBuffer (2, numSamples);
  103. AudioSourceChannelInfo info (tempBuffer);
  104. source->source->getNextAudioBlock (info);
  105. typedef AudioData::Pointer <AudioData::Int16,
  106. AudioData::LittleEndian,
  107. AudioData::Interleaved,
  108. AudioData::NonConst> CDSampleFormat;
  109. typedef AudioData::Pointer <AudioData::Float32,
  110. AudioData::NativeEndian,
  111. AudioData::NonInterleaved,
  112. AudioData::Const> SourceSampleFormat;
  113. CDSampleFormat left (buffer, 2);
  114. left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples);
  115. CDSampleFormat right (buffer + 2, 2);
  116. right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples);
  117. source->readPosition += numSamples;
  118. }
  119. return numSamples * 4;
  120. }
  121. return 0;
  122. }
  123. static uint32_t producePreGapForTrack (id self, SEL, DRTrack*, char* buffer,
  124. uint32_t bufferLength, uint64_t /*address*/,
  125. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  126. {
  127. zeromem (buffer, bufferLength);
  128. return bufferLength;
  129. }
  130. static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*,
  131. uint32_t /*bufferLength*/, uint64_t /*address*/,
  132. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  133. {
  134. return true;
  135. }
  136. };
  137. struct OpenDiskDevice
  138. {
  139. OpenDiskDevice (DRDevice* device_)
  140. : device (device_),
  141. tracks ([[NSMutableArray alloc] init]),
  142. underrunProtection (true)
  143. {
  144. }
  145. ~OpenDiskDevice()
  146. {
  147. [tracks release];
  148. }
  149. void addSourceTrack (AudioSource* source, int numSamples)
  150. {
  151. if (source != nullptr)
  152. {
  153. const int numFrames = (numSamples + 587) / 588;
  154. static AudioTrackProducerClass cls;
  155. NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
  156. withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
  157. DRTrack* track = [[DRTrack alloc] initWithProducer: producer];
  158. {
  159. NSMutableDictionary* p = [[track properties] mutableCopy];
  160. [p setObject: [DRMSF msfWithFrames: 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 CharPointer_UTF8 ([err UTF8String]);
  209. }
  210. [device releaseMediaReservation];
  211. [device releaseExclusiveAccess];
  212. return String::empty;
  213. }
  214. DRDevice* device;
  215. NSMutableArray* tracks;
  216. bool underrunProtection;
  217. };
  218. //==============================================================================
  219. class AudioCDBurner::Pimpl : public Timer
  220. {
  221. public:
  222. Pimpl (AudioCDBurner& owner_, const int deviceIndex)
  223. : device (0), owner (owner_)
  224. {
  225. DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex];
  226. if (dev != nil)
  227. {
  228. device = new OpenDiskDevice (dev);
  229. lastState = getDiskState();
  230. startTimer (1000);
  231. }
  232. }
  233. ~Pimpl()
  234. {
  235. stopTimer();
  236. }
  237. void timerCallback()
  238. {
  239. const DiskState state = getDiskState();
  240. if (state != lastState)
  241. {
  242. lastState = state;
  243. owner.sendChangeMessage();
  244. }
  245. }
  246. DiskState getDiskState() const
  247. {
  248. if ([device->device isValid])
  249. {
  250. NSDictionary* status = [device->device status];
  251. NSString* state = [status objectForKey: DRDeviceMediaStateKey];
  252. if ([state isEqualTo: DRDeviceMediaStateNone])
  253. {
  254. if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue])
  255. return trayOpen;
  256. return noDisc;
  257. }
  258. if ([state isEqualTo: DRDeviceMediaStateMediaPresent])
  259. {
  260. if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0)
  261. return writableDiskPresent;
  262. else
  263. return readOnlyDiskPresent;
  264. }
  265. }
  266. return unknown;
  267. }
  268. bool openTray() { return [device->device isValid] && [device->device ejectMedia]; }
  269. Array<int> getAvailableWriteSpeeds() const
  270. {
  271. Array<int> results;
  272. if ([device->device isValid])
  273. {
  274. NSArray* speeds = [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey];
  275. for (unsigned int i = 0; i < [speeds count]; ++i)
  276. {
  277. const int kbPerSec = [[speeds objectAtIndex: i] intValue];
  278. results.add (kbPerSec / kilobytesPerSecond1x);
  279. }
  280. }
  281. return results;
  282. }
  283. bool setBufferUnderrunProtection (const bool shouldBeEnabled)
  284. {
  285. if ([device->device isValid])
  286. {
  287. device->underrunProtection = shouldBeEnabled;
  288. return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue];
  289. }
  290. return false;
  291. }
  292. int getNumAvailableAudioBlocks() const
  293. {
  294. return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey]
  295. objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
  296. }
  297. ScopedPointer<OpenDiskDevice> device;
  298. private:
  299. DiskState lastState;
  300. AudioCDBurner& owner;
  301. };
  302. //==============================================================================
  303. AudioCDBurner::AudioCDBurner (const int deviceIndex)
  304. {
  305. pimpl = new Pimpl (*this, deviceIndex);
  306. }
  307. AudioCDBurner::~AudioCDBurner()
  308. {
  309. }
  310. AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
  311. {
  312. ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex));
  313. if (b->pimpl->device == nil)
  314. b = 0;
  315. return b.release();
  316. }
  317. namespace
  318. {
  319. NSArray* findDiskBurnerDevices()
  320. {
  321. NSMutableArray* results = [NSMutableArray array];
  322. NSArray* devs = [DRDevice devices];
  323. for (int i = 0; i < [devs count]; ++i)
  324. {
  325. NSDictionary* dic = [[devs objectAtIndex: i] info];
  326. NSString* name = [dic valueForKey: DRDeviceProductNameKey];
  327. if (name != nil)
  328. [results addObject: name];
  329. }
  330. return results;
  331. }
  332. }
  333. StringArray AudioCDBurner::findAvailableDevices()
  334. {
  335. NSArray* names = findDiskBurnerDevices();
  336. StringArray s;
  337. for (unsigned int i = 0; i < [names count]; ++i)
  338. s.add (CharPointer_UTF8 ([[names objectAtIndex: i] UTF8String]));
  339. return s;
  340. }
  341. AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
  342. {
  343. return pimpl->getDiskState();
  344. }
  345. bool AudioCDBurner::isDiskPresent() const
  346. {
  347. return getDiskState() == writableDiskPresent;
  348. }
  349. bool AudioCDBurner::openTray()
  350. {
  351. return pimpl->openTray();
  352. }
  353. AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
  354. {
  355. const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
  356. DiskState oldState = getDiskState();
  357. DiskState newState = oldState;
  358. while (newState == oldState && Time::currentTimeMillis() < timeout)
  359. {
  360. newState = getDiskState();
  361. Thread::sleep (100);
  362. }
  363. return newState;
  364. }
  365. Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
  366. {
  367. return pimpl->getAvailableWriteSpeeds();
  368. }
  369. bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
  370. {
  371. return pimpl->setBufferUnderrunProtection (shouldBeEnabled);
  372. }
  373. int AudioCDBurner::getNumAvailableAudioBlocks() const
  374. {
  375. return pimpl->getNumAvailableAudioBlocks();
  376. }
  377. bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps)
  378. {
  379. if ([pimpl->device->device isValid])
  380. {
  381. pimpl->device->addSourceTrack (source, numSamps);
  382. return true;
  383. }
  384. return false;
  385. }
  386. String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener,
  387. bool ejectDiscAfterwards,
  388. bool performFakeBurnForTesting,
  389. int writeSpeed)
  390. {
  391. if ([pimpl->device->device isValid])
  392. return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed);
  393. return "Couldn't open or write to the CD device";
  394. }