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.

458 lines
15KB

  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. const int kilobytesPerSecond1x = 176;
  20. struct AudioTrackProducerClass : public ObjCClass <NSObject>
  21. {
  22. AudioTrackProducerClass() : ObjCClass <NSObject> ("JUCEAudioTrackProducer_")
  23. {
  24. addIvar<AudioSourceHolder*> ("source");
  25. addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v");
  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. addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
  36. produceDataForTrack, "I@:@^cIQI^I");
  37. registerClass();
  38. }
  39. struct AudioSourceHolder
  40. {
  41. AudioSourceHolder (AudioSource* s, int numFrames)
  42. : source (s), readPosition (0), lengthInFrames (numFrames)
  43. {
  44. }
  45. ~AudioSourceHolder()
  46. {
  47. if (source != nullptr)
  48. source->releaseResources();
  49. }
  50. ScopedPointer<AudioSource> source;
  51. int readPosition, lengthInFrames;
  52. };
  53. private:
  54. static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source)
  55. {
  56. self = sendSuperclassMessage (self, @selector (init));
  57. object_setInstanceVariable (self, "source", source);
  58. return self;
  59. }
  60. static AudioSourceHolder* getSource (id self)
  61. {
  62. return getIvar<AudioSourceHolder*> (self, "source");
  63. }
  64. static void dealloc (id self, SEL)
  65. {
  66. delete getSource (self);
  67. sendSuperclassMessage (self, @selector (dealloc));
  68. }
  69. static void cleanupTrackAfterBurn (id, SEL, DRTrack*) {}
  70. static BOOL cleanupTrackAfterVerification (id, SEL, DRTrack*) { return true; }
  71. static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*)
  72. {
  73. return static_cast<uint64_t> (getSource (self)->lengthInFrames);
  74. }
  75. static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*)
  76. {
  77. if (AudioSourceHolder* const source = getSource (self))
  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. if (AudioSourceHolder* const source = getSource (self))
  87. source->source->prepareToPlay (44100 / 75, 44100);
  88. return true;
  89. }
  90. static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer,
  91. uint32_t bufferLength, uint64_t /*address*/,
  92. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  93. {
  94. if (AudioSourceHolder* const source = getSource (self))
  95. {
  96. const int numSamples = jmin ((int) bufferLength / 4,
  97. (source->lengthInFrames * (44100 / 75)) - source->readPosition);
  98. if (numSamples > 0)
  99. {
  100. AudioSampleBuffer tempBuffer (2, numSamples);
  101. AudioSourceChannelInfo info (tempBuffer);
  102. source->source->getNextAudioBlock (info);
  103. typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
  104. typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
  105. CDSampleFormat left (buffer, 2);
  106. left.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (0)), numSamples);
  107. CDSampleFormat right (buffer + 2, 2);
  108. right.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (1)), numSamples);
  109. source->readPosition += numSamples;
  110. }
  111. return static_cast<uint32_t> (numSamples * 4);
  112. }
  113. return 0;
  114. }
  115. static uint32_t producePreGapForTrack (id, SEL, DRTrack*, char* buffer,
  116. uint32_t bufferLength, uint64_t /*address*/,
  117. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  118. {
  119. zeromem (buffer, bufferLength);
  120. return bufferLength;
  121. }
  122. static BOOL verifyDataForTrack (id, SEL, DRTrack*, const char*,
  123. uint32_t /*bufferLength*/, uint64_t /*address*/,
  124. uint32_t /*blockSize*/, uint32_t* /*flags*/)
  125. {
  126. return true;
  127. }
  128. };
  129. struct OpenDiskDevice
  130. {
  131. OpenDiskDevice (DRDevice* d)
  132. : device (d),
  133. tracks ([[NSMutableArray alloc] init]),
  134. underrunProtection (true)
  135. {
  136. }
  137. ~OpenDiskDevice()
  138. {
  139. [tracks release];
  140. }
  141. void addSourceTrack (AudioSource* source, int numSamples)
  142. {
  143. if (source != nullptr)
  144. {
  145. const int numFrames = (numSamples + 587) / 588;
  146. static AudioTrackProducerClass cls;
  147. NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
  148. withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
  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 : public 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 = new OpenDiskDevice (dev);
  219. lastState = getDiskState();
  220. startTimer (1000);
  221. }
  222. }
  223. ~Pimpl()
  224. {
  225. stopTimer();
  226. }
  227. void timerCallback() override
  228. {
  229. const DiskState state = getDiskState();
  230. if (state != lastState)
  231. {
  232. lastState = state;
  233. owner.sendChangeMessage();
  234. }
  235. }
  236. DiskState getDiskState() const
  237. {
  238. if ([device->device isValid])
  239. {
  240. NSDictionary* status = [device->device status];
  241. NSString* state = [status objectForKey: DRDeviceMediaStateKey];
  242. if ([state isEqualTo: DRDeviceMediaStateNone])
  243. {
  244. if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue])
  245. return trayOpen;
  246. return noDisc;
  247. }
  248. if ([state isEqualTo: DRDeviceMediaStateMediaPresent])
  249. {
  250. if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0)
  251. return writableDiskPresent;
  252. return readOnlyDiskPresent;
  253. }
  254. }
  255. return unknown;
  256. }
  257. bool openTray() { return [device->device isValid] && [device->device ejectMedia]; }
  258. Array<int> getAvailableWriteSpeeds() const
  259. {
  260. Array<int> results;
  261. if ([device->device isValid])
  262. for (id kbPerSec in [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey])
  263. results.add ([kbPerSec intValue] / kilobytesPerSecond1x);
  264. return results;
  265. }
  266. bool setBufferUnderrunProtection (const bool shouldBeEnabled)
  267. {
  268. if ([device->device isValid])
  269. {
  270. device->underrunProtection = shouldBeEnabled;
  271. return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue];
  272. }
  273. return false;
  274. }
  275. int getNumAvailableAudioBlocks() const
  276. {
  277. return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey]
  278. objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
  279. }
  280. ScopedPointer<OpenDiskDevice> device;
  281. private:
  282. DiskState lastState;
  283. AudioCDBurner& owner;
  284. };
  285. //==============================================================================
  286. AudioCDBurner::AudioCDBurner (const int deviceIndex)
  287. {
  288. pimpl = new Pimpl (*this, deviceIndex);
  289. }
  290. AudioCDBurner::~AudioCDBurner()
  291. {
  292. }
  293. AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
  294. {
  295. ScopedPointer<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. }