@@ -295,7 +295,7 @@ void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const | |||
const String name (resources[i]->name); | |||
const MemoryBlock& mb = resources[i]->data; | |||
defs << "// JUCER_RESOURCE: " << name << ", " << mb.getSize() | |||
defs << "// JUCER_RESOURCE: " << name << ", " << (int) mb.getSize() | |||
<< ", \"" | |||
<< File (resources[i]->originalFilename) | |||
.getRelativePathFrom (code.document->getFile()) | |||
@@ -311,7 +311,7 @@ void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const | |||
MemoryOutputStream out (65536, 16384); | |||
int charsOnLine = line1.length(); | |||
for (int j = 0; j < mb.getSize(); ++j) | |||
for (size_t j = 0; j < mb.getSize(); ++j) | |||
{ | |||
const int num = ((int) (unsigned char) mb[j]); | |||
out << num << ','; | |||
@@ -338,7 +338,7 @@ void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const | |||
<< " = (const char*) resource_" << code.className << "_" << name | |||
<< ";\nconst int " | |||
<< code.className << "::" << name << "Size = " | |||
<< mb.getSize() | |||
<< (int) mb.getSize() | |||
<< ";\n\n"; | |||
} | |||
@@ -5902,10 +5902,14 @@ static const String parseAbsolutePath (String path) | |||
{ | |||
if (path[1] != File::separator) | |||
{ | |||
jassertfalse // using a filename that starts with a slash is a bit dodgy on | |||
// Windows, because it needs a drive letter, which in this case | |||
// we'll take from the CWD.. but this is a bit of an assumption that | |||
// could be wrong.. | |||
/* When you supply a raw string to the File object constructor, it must be an absolute path. | |||
If you're trying to parse a string that may be either a relative path or an absolute path, | |||
you MUST provide a context against which the partial path can be evaluated - you can do | |||
this by simply using File::getChildFile() instead of the File constructor. E.g. saying | |||
"File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute | |||
path if that's what was supplied, or would evaluate a partial path relative to the CWD. | |||
*/ | |||
jassertfalse | |||
path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; | |||
} | |||
@@ -5915,10 +5919,14 @@ static const String parseAbsolutePath (String path) | |||
if (path.isEmpty()) | |||
return String::empty; | |||
jassertfalse // using a partial filename is a bad way to initialise a file, because | |||
// we don't know what directory to put it in. | |||
// Here we'll assume it's in the CWD, but this might not be what was | |||
// intended.. | |||
/* When you supply a raw string to the File object constructor, it must be an absolute path. | |||
If you're trying to parse a string that may be either a relative path or an absolute path, | |||
you MUST provide a context against which the partial path can be evaluated - you can do | |||
this by simply using File::getChildFile() instead of the File constructor. E.g. saying | |||
"File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute | |||
path if that's what was supplied, or would evaluate a partial path relative to the CWD. | |||
*/ | |||
jassertfalse | |||
return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); | |||
} | |||
@@ -5957,16 +5965,14 @@ static const String parseAbsolutePath (String path) | |||
} | |||
else if (! path.startsWithChar (File::separator)) | |||
{ | |||
while (path.startsWith (T("./"))) | |||
path = path.substring (2); | |||
if (path.isEmpty()) | |||
return String::empty; | |||
jassertfalse // using a partial filename is a bad way to initialise a file, because | |||
// we don't know what directory to put it in. | |||
// Here we'll assume it's in the CWD, but this might not be what was | |||
// intended.. | |||
/* When you supply a raw string to the File object constructor, it must be an absolute path. | |||
If you're trying to parse a string that may be either a relative path or an absolute path, | |||
you MUST provide a context against which the partial path can be evaluated - you can do | |||
this by simply using File::getChildFile() instead of the File constructor. E.g. saying | |||
"File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute | |||
path if that's what was supplied, or would evaluate a partial path relative to the CWD. | |||
*/ | |||
jassert (path.startsWith (T("./"))); // (assume that a path "./xyz" is deliberately intended to be relative to the CWD) | |||
return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); | |||
} | |||
@@ -21866,6 +21872,14 @@ struct SMPLChunk | |||
} | |||
} PACKED; | |||
struct ExtensibleWavSubFormat | |||
{ | |||
uint32 data1; | |||
uint16 data2; | |||
uint16 data3; | |||
uint8 data4[8]; | |||
} PACKED; | |||
#if JUCE_MSVC | |||
#pragma pack (pop) | |||
#endif | |||
@@ -21910,7 +21924,7 @@ public: | |||
if (chunkType == chunkName ("fmt ")) | |||
{ | |||
// read the format chunk | |||
const short format = input->readShort(); | |||
const unsigned short format = input->readShort(); | |||
const short numChans = input->readShort(); | |||
sampleRate = input->readInt(); | |||
const int bytesPerSec = input->readInt(); | |||
@@ -21920,9 +21934,41 @@ public: | |||
bitsPerSample = 8 * bytesPerFrame / numChans; | |||
if (format == 3) | |||
{ | |||
usesFloatingPointData = true; | |||
} | |||
else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) | |||
{ | |||
if (length < 40) // too short | |||
{ | |||
bytesPerFrame = 0; | |||
} | |||
else | |||
{ | |||
input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask | |||
ExtensibleWavSubFormat subFormat; | |||
subFormat.data1 = input->readInt(); | |||
subFormat.data2 = input->readShort(); | |||
subFormat.data3 = input->readShort(); | |||
input->read (subFormat.data4, sizeof (subFormat.data4)); | |||
const ExtensibleWavSubFormat pcmFormat | |||
= { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) | |||
{ | |||
const ExtensibleWavSubFormat ambisonicFormat | |||
= { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; | |||
if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) | |||
bytesPerFrame = 0; | |||
} | |||
} | |||
} | |||
else if (format != 1) | |||
{ | |||
bytesPerFrame = 0; | |||
} | |||
hasGotType = true; | |||
} | |||
@@ -221857,28 +221903,23 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||
{ | |||
AudioCDBurner* b = new AudioCDBurner (deviceIndex); | |||
ScopedPointer<AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||
if (b->internal == 0) | |||
deleteAndZero (b); | |||
if (b->pimpl == 0) | |||
b = 0; | |||
return b; | |||
return b.release(); | |||
} | |||
class CDBurnerInfo : public IDiscMasterProgressEvents | |||
class AudioCDBurner::Pimpl : public IDiscMasterProgressEvents | |||
{ | |||
public: | |||
CDBurnerInfo() | |||
: refCount (1), | |||
progress (0), | |||
shouldCancel (false), | |||
listener (0) | |||
Pimpl() | |||
: listener (0), progress (0), shouldCancel (false), refCount (1) | |||
{ | |||
} | |||
~CDBurnerInfo() | |||
{ | |||
} | |||
~Pimpl() {} | |||
HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result) | |||
{ | |||
@@ -221925,6 +221966,50 @@ public: | |||
HRESULT __stdcall NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||
HRESULT __stdcall NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||
int getIntProperty (const LPOLESTR name, const int defaultReturn) const | |||
{ | |||
ComSmartPtr<IPropertyStorage> prop; | |||
if (FAILED (discRecorder->GetRecorderProperties (&prop))) | |||
return defaultReturn; | |||
PROPSPEC iPropSpec; | |||
iPropSpec.ulKind = PRSPEC_LPWSTR; | |||
iPropSpec.lpwstr = name; | |||
PROPVARIANT iPropVariant; | |||
return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)) | |||
? defaultReturn : (int) iPropVariant.lVal; | |||
} | |||
bool setIntProperty (const LPOLESTR name, const int value) const | |||
{ | |||
ComSmartPtr<IPropertyStorage> prop; | |||
if (FAILED (discRecorder->GetRecorderProperties (&prop))) | |||
return false; | |||
PROPSPEC iPropSpec; | |||
iPropSpec.ulKind = PRSPEC_LPWSTR; | |||
iPropSpec.lpwstr = name; | |||
PROPVARIANT iPropVariant; | |||
if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))) | |||
return false; | |||
iPropVariant.lVal = (long) value; | |||
return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt)) | |||
&& SUCCEEDED (discRecorder->SetRecorderProperties (prop)); | |||
} | |||
class DiskOpener | |||
{ | |||
public: | |||
DiskOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); } | |||
~DiskOpener() { pimpl.discRecorder->Close(); } | |||
private: | |||
Pimpl& pimpl; | |||
}; | |||
IDiscMaster* discMaster; | |||
IDiscRecorder* discRecorder; | |||
IRedbookDiscMaster* redbook; | |||
@@ -221937,7 +222022,6 @@ private: | |||
}; | |||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
: internal (0) | |||
{ | |||
IDiscMaster* discMaster; | |||
IDiscRecorder* dr = enumCDBurners (0, deviceIndex, &discMaster); | |||
@@ -221946,69 +222030,115 @@ AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
{ | |||
IRedbookDiscMaster* redbook; | |||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | |||
hr = discMaster->SetActiveDiscRecorder (dr); | |||
CDBurnerInfo* const info = new CDBurnerInfo(); | |||
internal = info; | |||
info->discMaster = discMaster; | |||
info->discRecorder = dr; | |||
info->redbook = redbook; | |||
pimpl = new Pimpl(); | |||
pimpl->discMaster = discMaster; | |||
pimpl->discRecorder = dr; | |||
pimpl->redbook = redbook; | |||
} | |||
} | |||
AudioCDBurner::~AudioCDBurner() | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
if (info != 0) | |||
if (pimpl != 0) | |||
{ | |||
info->discRecorder->Close(); | |||
info->redbook->Release(); | |||
info->discRecorder->Release(); | |||
info->discMaster->Release(); | |||
pimpl->discRecorder->Close(); | |||
pimpl->redbook->Release(); | |||
pimpl->discRecorder->Release(); | |||
pimpl->discMaster->Release(); | |||
info->Release(); | |||
pimpl.release()->Release(); | |||
} | |||
} | |||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||
{ | |||
const Pimpl::DiskOpener opener (*pimpl); | |||
long type, flags; | |||
HRESULT hr = pimpl->discRecorder->QueryMediaType (&type, &flags); | |||
if (FAILED (hr)) | |||
return unknown; | |||
if (type != 0 && (flags & MEDIA_WRITABLE) != 0) | |||
return writableDiskPresent; | |||
if (type == 0) | |||
return noDisc; | |||
else | |||
return readOnlyDiskPresent; | |||
} | |||
bool AudioCDBurner::isDiskPresent() const | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
return getDiskState() == writableDiskPresent; | |||
} | |||
HRESULT hr = info->discRecorder->OpenExclusive(); | |||
bool AudioCDBurner::openTray() | |||
{ | |||
const Pimpl::DiskOpener opener (*pimpl); | |||
return pimpl->discRecorder->Eject() == S_OK; | |||
} | |||
long type, flags; | |||
hr = info->discRecorder->QueryMediaType (&type, &flags); | |||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||
{ | |||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||
DiskState oldState = getDiskState(); | |||
DiskState newState = oldState; | |||
while (newState == oldState && Time::currentTimeMillis() < timeout) | |||
{ | |||
newState = getDiskState(); | |||
Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis()))); | |||
} | |||
return newState; | |||
} | |||
const Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||
{ | |||
Array<int> results; | |||
const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1); | |||
const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 }; | |||
for (int i = 0; i < numElementsInArray (speeds); ++i) | |||
if (speeds[i] <= maxSpeed) | |||
results.add (speeds[i]); | |||
info->discRecorder->Close(); | |||
return hr == S_OK && type != 0 && (flags & MEDIA_WRITABLE) != 0; | |||
results.addIfNotAlreadyThere (maxSpeed); | |||
return results; | |||
} | |||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
{ | |||
if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0) | |||
return false; | |||
pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0); | |||
return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0; | |||
} | |||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
long blocksFree = 0; | |||
info->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||
return blocksFree; | |||
} | |||
const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||
const bool ejectDiscAfterwards, | |||
const bool performFakeBurnForTesting) | |||
const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, int writeSpeed) | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
info->listener = listener; | |||
info->progress = 0; | |||
info->shouldCancel = false; | |||
pimpl->listener = listener; | |||
pimpl->progress = 0; | |||
pimpl->shouldCancel = false; | |||
UINT_PTR cookie; | |||
HRESULT hr = info->discMaster->ProgressAdvise (info, &cookie); | |||
HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl, &cookie); | |||
hr = info->discMaster->RecordDisc (performFakeBurnForTesting, | |||
ejectDiscAfterwards); | |||
hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting, | |||
ejectDiscAfterwards); | |||
String error; | |||
if (hr != S_OK) | |||
@@ -222023,29 +222153,28 @@ const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||
error = e; | |||
} | |||
info->discMaster->ProgressUnadvise (cookie); | |||
info->listener = 0; | |||
pimpl->discMaster->ProgressUnadvise (cookie); | |||
pimpl->listener = 0; | |||
return error; | |||
} | |||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||
{ | |||
if (source == 0) | |||
if (audioSource == 0) | |||
return false; | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
ScopedPointer<AudioSource> source (audioSource); | |||
long bytesPerBlock; | |||
HRESULT hr = info->redbook->GetAudioBlockSize (&bytesPerBlock); | |||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock); | |||
const int samplesPerBlock = bytesPerBlock / 4; | |||
bool ok = true; | |||
hr = info->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
HeapBlock <byte> buffer (bytesPerBlock); | |||
AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | |||
int samplesDone = 0; | |||
@@ -222071,9 +222200,9 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||
AudioDataConverters::convertFloatToInt16LE (sourceBuffer.getSampleData (1, 0), | |||
buffer + 2, samplesPerBlock, 4); | |||
hr = info->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||
hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||
if (hr != S_OK) | |||
if (FAILED (hr)) | |||
ok = false; | |||
samplesDone += samplesPerBlock; | |||
@@ -222082,10 +222211,7 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||
break; | |||
} | |||
hr = info->redbook->CloseAudioTrack(); | |||
delete source; | |||
hr = pimpl->redbook->CloseAudioTrack(); | |||
return ok && hr == S_OK; | |||
} | |||
@@ -228305,6 +228431,29 @@ void JUCE_CALLTYPE Thread::sleep (int millisecs) | |||
const tchar File::separator = T('/'); | |||
const tchar* File::separatorString = T("/"); | |||
const File File::getCurrentWorkingDirectory() | |||
{ | |||
HeapBlock<char> heapBuffer; | |||
char localBuffer [1024]; | |||
char* cwd = getcwd (localBuffer, sizeof (localBuffer) - 1); | |||
int bufferSize = 4096; | |||
while (cwd == 0 && errno == ERANGE) | |||
{ | |||
heapBuffer.malloc (bufferSize); | |||
cwd = getcwd (heapBuffer, bufferSize - 1); | |||
bufferSize += 1024; | |||
} | |||
return File (String::fromUTF8 ((const uint8*) cwd)); | |||
} | |||
bool File::setAsCurrentWorkingDirectory() const | |||
{ | |||
return chdir (getFullPathName().toUTF8()) == 0; | |||
} | |||
bool juce_copyFile (const String& s, const String& d); | |||
static bool juce_stat (const String& fileName, struct stat& info) | |||
@@ -228455,7 +228604,7 @@ const File juce_getExecutableFile() | |||
{ | |||
Dl_info exeInfo; | |||
dladdr ((const void*) juce_getExecutableFile, &exeInfo); | |||
return File (exeInfo.dli_fname); | |||
return File (String::fromUTF8 ((const uint8*) exeInfo.dli_fname)); | |||
} | |||
// if this file doesn't exist, find a parent of it that does.. | |||
@@ -228850,17 +228999,6 @@ const File File::getSpecialLocation (const SpecialLocationType type) | |||
return File::nonexistent; | |||
} | |||
const File File::getCurrentWorkingDirectory() | |||
{ | |||
char buf [2048]; | |||
return File (String::fromUTF8 ((const uint8*) getcwd (buf, sizeof (buf)))); | |||
} | |||
bool File::setAsCurrentWorkingDirectory() const | |||
{ | |||
return chdir (getFullPathName().toUTF8()) == 0; | |||
} | |||
const String File::getVersion() const | |||
{ | |||
return String::empty; // xxx not yet implemented | |||
@@ -238100,6 +238238,29 @@ void JUCE_CALLTYPE Thread::sleep (int millisecs) | |||
const tchar File::separator = T('/'); | |||
const tchar* File::separatorString = T("/"); | |||
const File File::getCurrentWorkingDirectory() | |||
{ | |||
HeapBlock<char> heapBuffer; | |||
char localBuffer [1024]; | |||
char* cwd = getcwd (localBuffer, sizeof (localBuffer) - 1); | |||
int bufferSize = 4096; | |||
while (cwd == 0 && errno == ERANGE) | |||
{ | |||
heapBuffer.malloc (bufferSize); | |||
cwd = getcwd (heapBuffer, bufferSize - 1); | |||
bufferSize += 1024; | |||
} | |||
return File (String::fromUTF8 ((const uint8*) cwd)); | |||
} | |||
bool File::setAsCurrentWorkingDirectory() const | |||
{ | |||
return chdir (getFullPathName().toUTF8()) == 0; | |||
} | |||
bool juce_copyFile (const String& s, const String& d); | |||
static bool juce_stat (const String& fileName, struct stat& info) | |||
@@ -238250,7 +238411,7 @@ const File juce_getExecutableFile() | |||
{ | |||
Dl_info exeInfo; | |||
dladdr ((const void*) juce_getExecutableFile, &exeInfo); | |||
return File (exeInfo.dli_fname); | |||
return File (String::fromUTF8 ((const uint8*) exeInfo.dli_fname)); | |||
} | |||
// if this file doesn't exist, find a parent of it that does.. | |||
@@ -238664,19 +238825,6 @@ const File File::getSpecialLocation (const SpecialLocationType type) | |||
return File::nonexistent; | |||
} | |||
const File File::getCurrentWorkingDirectory() | |||
{ | |||
char buf [2048]; | |||
getcwd (buf, sizeof(buf)); | |||
return File (PlatformUtilities::convertToPrecomposedUnicode (buf)); | |||
} | |||
bool File::setAsCurrentWorkingDirectory() const | |||
{ | |||
return chdir (getFullPathName().toUTF8()) == 0; | |||
} | |||
const String File::getVersion() const | |||
{ | |||
const ScopedAutoReleasePool pool; | |||
@@ -248313,24 +248461,25 @@ bool juce_OpenQuickTimeMovieFromStream (InputStream* movieStream, Movie& result, | |||
// compiled on its own). | |||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDBURNER | |||
const int kilobytesPerSecond1x = 176; | |||
END_JUCE_NAMESPACE | |||
#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | |||
@interface OpenDiskDevice : NSObject | |||
{ | |||
@public | |||
DRDevice* device; | |||
NSMutableArray* tracks; | |||
bool underrunProtection; | |||
} | |||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device; | |||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device; | |||
- (void) dealloc; | |||
- (bool) isDiskPresent; | |||
- (int) getNumAvailableAudioBlocks; | |||
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; | |||
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting; | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed; | |||
@end | |||
#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer) | |||
@@ -248370,12 +248519,13 @@ END_JUCE_NAMESPACE | |||
@implementation OpenDiskDevice | |||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device_ | |||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ | |||
{ | |||
[super init]; | |||
device = device_; | |||
tracks = [[NSMutableArray alloc] init]; | |||
underrunProtection = true; | |||
return self; | |||
} | |||
@@ -248385,19 +248535,6 @@ END_JUCE_NAMESPACE | |||
[super dealloc]; | |||
} | |||
- (bool) isDiskPresent | |||
{ | |||
return [device isValid] | |||
&& [[[device status] objectForKey: DRDeviceMediaStateKey] | |||
isEqualTo: DRDeviceMediaStateMediaPresent]; | |||
} | |||
- (int) getNumAvailableAudioBlocks | |||
{ | |||
return [[[[device status] objectForKey: DRDeviceMediaInfoKey] | |||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
} | |||
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) numSamples_ | |||
{ | |||
AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_]; | |||
@@ -248411,7 +248548,7 @@ END_JUCE_NAMESPACE | |||
} | |||
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed | |||
{ | |||
DRBurn* burn = [DRBurn burnForDevice: device]; | |||
@@ -248427,8 +248564,14 @@ END_JUCE_NAMESPACE | |||
[d autorelease]; | |||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | |||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) | |||
forKey: DRBurnCompletionActionKey]; | |||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; | |||
if (burnSpeed > 0) | |||
[d setObject: [NSNumber numberWithFloat: burnSpeed * JUCE_NAMESPACE::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
if (! underrunProtection) | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
[burn setProperties: d]; | |||
[burn writeLayout: tracks]; | |||
@@ -248599,27 +248742,123 @@ END_JUCE_NAMESPACE | |||
BEGIN_JUCE_NAMESPACE | |||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
: internal (0) | |||
class AudioCDBurner::Pimpl : public Timer | |||
{ | |||
OpenDiskDevice* dev = [[OpenDiskDevice alloc] initWithDevice: [[DRDevice devices] objectAtIndex: deviceIndex]]; | |||
public: | |||
Pimpl (AudioCDBurner& owner_, const int deviceIndex) | |||
: device (0), owner (owner_) | |||
{ | |||
DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; | |||
if (dev != 0) | |||
{ | |||
device = [[OpenDiskDevice alloc] initWithDRDevice: dev]; | |||
lastState = getDiskState(); | |||
startTimer (1000); | |||
} | |||
} | |||
~Pimpl() | |||
{ | |||
stopTimer(); | |||
[device release]; | |||
} | |||
void timerCallback() | |||
{ | |||
const DiskState state = getDiskState(); | |||
if (state != lastState) | |||
{ | |||
lastState = state; | |||
owner.sendChangeMessage (&owner); | |||
} | |||
} | |||
DiskState getDiskState() const | |||
{ | |||
if ([device->device isValid]) | |||
{ | |||
NSDictionary* status = [device->device status]; | |||
NSString* state = [status objectForKey: DRDeviceMediaStateKey]; | |||
if ([state isEqualTo: DRDeviceMediaStateNone]) | |||
{ | |||
if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue]) | |||
return trayOpen; | |||
return noDisc; | |||
} | |||
if ([state isEqualTo: DRDeviceMediaStateMediaPresent]) | |||
{ | |||
if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0) | |||
return writableDiskPresent; | |||
else | |||
return readOnlyDiskPresent; | |||
} | |||
} | |||
return unknown; | |||
} | |||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||
const Array<int> getAvailableWriteSpeeds() const | |||
{ | |||
Array<int> results; | |||
if ([device->device isValid]) | |||
{ | |||
NSArray* speeds = [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey]; | |||
for (unsigned int i = 0; i < [speeds count]; ++i) | |||
{ | |||
const int kbPerSec = [[speeds objectAtIndex: i] intValue]; | |||
results.add (kbPerSec / kilobytesPerSecond1x); | |||
} | |||
} | |||
return results; | |||
} | |||
bool setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
{ | |||
if ([device->device isValid]) | |||
{ | |||
device->underrunProtection = shouldBeEnabled; | |||
return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue]; | |||
} | |||
return false; | |||
} | |||
int getNumAvailableAudioBlocks() const | |||
{ | |||
return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey] | |||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
} | |||
OpenDiskDevice* device; | |||
private: | |||
DiskState lastState; | |||
AudioCDBurner& owner; | |||
}; | |||
internal = (void*) dev; | |||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
{ | |||
pimpl = new Pimpl (*this, deviceIndex); | |||
} | |||
AudioCDBurner::~AudioCDBurner() | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
if (dev != 0) | |||
[dev release]; | |||
} | |||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||
{ | |||
ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||
if (b->internal == 0) | |||
if (b->pimpl->device == 0) | |||
b = 0; | |||
return b.release(); | |||
@@ -248657,27 +248896,56 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||
return s; | |||
} | |||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||
{ | |||
return pimpl->getDiskState(); | |||
} | |||
bool AudioCDBurner::isDiskPresent() const | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
return getDiskState() == writableDiskPresent; | |||
} | |||
return dev != 0 && [dev isDiskPresent]; | |||
bool AudioCDBurner::openTray() | |||
{ | |||
return pimpl->openTray(); | |||
} | |||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||
DiskState oldState = getDiskState(); | |||
DiskState newState = oldState; | |||
while (newState == oldState && Time::currentTimeMillis() < timeout) | |||
{ | |||
newState = getDiskState(); | |||
Thread::sleep (100); | |||
} | |||
return [dev getNumAvailableAudioBlocks]; | |||
return newState; | |||
} | |||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
const Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
return pimpl->getAvailableWriteSpeeds(); | |||
} | |||
if (dev != 0) | |||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
{ | |||
return pimpl->setBufferUnderrunProtection (shouldBeEnabled); | |||
} | |||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
{ | |||
return pimpl->getNumAvailableAudioBlocks(); | |||
} | |||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
{ | |||
if ([pimpl->device->device isValid]) | |||
{ | |||
[dev addSourceTrack: source numSamples: numSamps]; | |||
[pimpl->device addSourceTrack: source numSamples: numSamps]; | |||
return true; | |||
} | |||
@@ -248685,25 +248953,30 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
} | |||
const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener* listener, | |||
const bool ejectDiscAfterwards, | |||
const bool peformFakeBurnForTesting) | |||
bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, | |||
int writeSpeed) | |||
{ | |||
String error ("Couldn't open or write to the CD device"); | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
if (dev != 0) | |||
if ([pimpl->device->device isValid]) | |||
{ | |||
error = String::empty; | |||
[dev burn: listener | |||
errorString: &error | |||
[pimpl->device burn: listener | |||
errorString: &error | |||
ejectAfterwards: ejectDiscAfterwards | |||
isFake: peformFakeBurnForTesting]; | |||
isFake: performFakeBurnForTesting | |||
speed: writeSpeed]; | |||
} | |||
return error; | |||
} | |||
#endif | |||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDREADER | |||
void AudioCDReader::ejectDisk() | |||
{ | |||
const ScopedAutoReleasePool p; | |||
@@ -4377,7 +4377,7 @@ public: | |||
static const String descriptionOfSizeInBytes (const int64 bytes); | |||
const String& getFullPathName() const { return fullPath; } | |||
const String& getFullPathName() const throw() { return fullPath; } | |||
const String getFileName() const; | |||
@@ -14016,7 +14016,7 @@ public: | |||
#if JUCE_USE_CDBURNER | |||
class AudioCDBurner | |||
class AudioCDBurner : public ChangeBroadcaster | |||
{ | |||
public: | |||
@@ -14026,8 +14026,28 @@ public: | |||
~AudioCDBurner(); | |||
enum DiskState | |||
{ | |||
unknown, /**< An error condition, if the device isn't responding. */ | |||
trayOpen, /**< The drive is currently open. Note that a slot-loading drive | |||
may seem to be permanently open. */ | |||
noDisc, /**< The drive has no disk in it. */ | |||
writableDiskPresent, /**< The drive contains a writeable disk. */ | |||
readOnlyDiskPresent /**< The drive contains a read-only disk. */ | |||
}; | |||
DiskState getDiskState() const; | |||
bool isDiskPresent() const; | |||
bool openTray(); | |||
DiskState waitUntilStateChange (int timeOutMilliseconds); | |||
const Array<int> getAvailableWriteSpeeds() const; | |||
bool setBufferUnderrunProtection (const bool shouldBeEnabled); | |||
int getNumAvailableAudioBlocks() const; | |||
bool addAudioTrack (AudioSource* source, int numSamples); | |||
@@ -14042,15 +14062,20 @@ public: | |||
}; | |||
const String burn (BurnProgressListener* listener, | |||
const bool ejectDiscAfterwards, | |||
const bool peformFakeBurnForTesting); | |||
bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, | |||
int writeSpeed); | |||
void abortBurn(); | |||
juce_UseDebuggingNewOperator | |||
private: | |||
AudioCDBurner (const int deviceIndex); | |||
void* internal; | |||
class Pimpl; | |||
friend class ScopedPointer<Pimpl>; | |||
ScopedPointer<Pimpl> pimpl; | |||
}; | |||
#endif | |||
@@ -35,7 +35,7 @@ | |||
//============================================================================== | |||
/** | |||
*/ | |||
class AudioCDBurner | |||
class AudioCDBurner : public ChangeBroadcaster | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -55,10 +55,53 @@ public: | |||
~AudioCDBurner(); | |||
//============================================================================== | |||
/** Returns true if there's a writable disk in the drive. | |||
enum DiskState | |||
{ | |||
unknown, /**< An error condition, if the device isn't responding. */ | |||
trayOpen, /**< The drive is currently open. Note that a slot-loading drive | |||
may seem to be permanently open. */ | |||
noDisc, /**< The drive has no disk in it. */ | |||
writableDiskPresent, /**< The drive contains a writeable disk. */ | |||
readOnlyDiskPresent /**< The drive contains a read-only disk. */ | |||
}; | |||
/** Returns the current status of the device. | |||
To get informed when the drive's status changes, attach a ChangeListener to | |||
the AudioCDBurner. | |||
*/ | |||
DiskState getDiskState() const; | |||
/** Returns true if there's a writable disk in the drive. */ | |||
bool isDiskPresent() const; | |||
/** Sends an eject signal to the drive. | |||
The eject will happen asynchronously, so you can use getDiskState() and | |||
waitUntilStateChange() to monitor its progress. | |||
*/ | |||
bool openTray(); | |||
/** Blocks the current thread until the drive's state changes, or until the timeout expires. | |||
@returns the device's new state | |||
*/ | |||
DiskState waitUntilStateChange (int timeOutMilliseconds); | |||
//============================================================================== | |||
/** Returns the set of possible write speeds that the device can handle. | |||
These are as a multiple of 'normal' speed, so e.g. '24x' returns 24, etc. | |||
Note that if there's no media present in the drive, this value may be unavailable! | |||
@see setWriteSpeed, getWriteSpeed | |||
*/ | |||
const Array<int> getAvailableWriteSpeeds() const; | |||
//============================================================================== | |||
/** Tries to enable or disable buffer underrun safety on devices that support it. | |||
@returns true if it's now enabled. If the device doesn't support it, this | |||
will always return false. | |||
*/ | |||
bool setBufferUnderrunProtection (const bool shouldBeEnabled); | |||
//============================================================================== | |||
/** Returns the number of free blocks on the disk. | |||
There are 75 blocks per second, at 44100Hz. | |||
@@ -75,9 +118,9 @@ public: | |||
*/ | |||
bool addAudioTrack (AudioSource* source, int numSamples); | |||
/** | |||
Return true to cancel the current burn operation | |||
//============================================================================== | |||
/** Receives progress callbacks during a cd-burn operation. | |||
@see AudioCDBurner::burn() | |||
*/ | |||
class BurnProgressListener | |||
{ | |||
@@ -87,14 +130,32 @@ public: | |||
/** Called at intervals to report on the progress of the AudioCDBurner. | |||
To cancel the burn, return true from this. | |||
To cancel the burn, return true from this method. | |||
*/ | |||
virtual bool audioCDBurnProgress (float proportionComplete) = 0; | |||
}; | |||
/** Runs the burn process. | |||
This method will block until the operation is complete. | |||
@param listener the object to receive callbacks about progress | |||
@param ejectDiscAfterwards whether to eject the disk after the burn completes | |||
@param performFakeBurnForTesting if true, no data will actually be written to the disk | |||
@param writeSpeed one of the write speeds from getAvailableWriteSpeeds(), or | |||
0 or less to mean the fastest speed. | |||
*/ | |||
const String burn (BurnProgressListener* listener, | |||
const bool ejectDiscAfterwards, | |||
const bool peformFakeBurnForTesting); | |||
bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, | |||
int writeSpeed); | |||
/** If a burn operation is currently in progress, this tells it to stop | |||
as soon as possible. | |||
It's also possible to stop the burn process by returning true from | |||
BurnProgressListener::audioCDBurnProgress() | |||
*/ | |||
void abortBurn(); | |||
//============================================================================== | |||
juce_UseDebuggingNewOperator | |||
@@ -102,7 +163,9 @@ public: | |||
private: | |||
AudioCDBurner (const int deviceIndex); | |||
void* internal; | |||
class Pimpl; | |||
friend class ScopedPointer<Pimpl>; | |||
ScopedPointer<Pimpl> pimpl; | |||
}; | |||
@@ -28,24 +28,25 @@ | |||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDBURNER | |||
//============================================================================== | |||
const int kilobytesPerSecond1x = 176; | |||
END_JUCE_NAMESPACE | |||
#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | |||
@interface OpenDiskDevice : NSObject | |||
{ | |||
@public | |||
DRDevice* device; | |||
NSMutableArray* tracks; | |||
bool underrunProtection; | |||
} | |||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device; | |||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device; | |||
- (void) dealloc; | |||
- (bool) isDiskPresent; | |||
- (int) getNumAvailableAudioBlocks; | |||
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; | |||
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting; | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed; | |||
@end | |||
//============================================================================== | |||
@@ -87,12 +88,13 @@ END_JUCE_NAMESPACE | |||
//============================================================================== | |||
@implementation OpenDiskDevice | |||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device_ | |||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ | |||
{ | |||
[super init]; | |||
device = device_; | |||
tracks = [[NSMutableArray alloc] init]; | |||
underrunProtection = true; | |||
return self; | |||
} | |||
@@ -102,19 +104,6 @@ END_JUCE_NAMESPACE | |||
[super dealloc]; | |||
} | |||
- (bool) isDiskPresent | |||
{ | |||
return [device isValid] | |||
&& [[[device status] objectForKey: DRDeviceMediaStateKey] | |||
isEqualTo: DRDeviceMediaStateMediaPresent]; | |||
} | |||
- (int) getNumAvailableAudioBlocks | |||
{ | |||
return [[[[device status] objectForKey: DRDeviceMediaInfoKey] | |||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
} | |||
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) numSamples_ | |||
{ | |||
AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_]; | |||
@@ -128,7 +117,7 @@ END_JUCE_NAMESPACE | |||
} | |||
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed | |||
{ | |||
DRBurn* burn = [DRBurn burnForDevice: device]; | |||
@@ -144,8 +133,14 @@ END_JUCE_NAMESPACE | |||
[d autorelease]; | |||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | |||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) | |||
forKey: DRBurnCompletionActionKey]; | |||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; | |||
if (burnSpeed > 0) | |||
[d setObject: [NSNumber numberWithFloat: burnSpeed * JUCE_NAMESPACE::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
if (! underrunProtection) | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
[burn setProperties: d]; | |||
[burn writeLayout: tracks]; | |||
@@ -320,27 +315,124 @@ END_JUCE_NAMESPACE | |||
BEGIN_JUCE_NAMESPACE | |||
//============================================================================== | |||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
: internal (0) | |||
class AudioCDBurner::Pimpl : public Timer | |||
{ | |||
OpenDiskDevice* dev = [[OpenDiskDevice alloc] initWithDevice: [[DRDevice devices] objectAtIndex: deviceIndex]]; | |||
public: | |||
Pimpl (AudioCDBurner& owner_, const int deviceIndex) | |||
: device (0), owner (owner_) | |||
{ | |||
DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; | |||
if (dev != 0) | |||
{ | |||
device = [[OpenDiskDevice alloc] initWithDRDevice: dev]; | |||
lastState = getDiskState(); | |||
startTimer (1000); | |||
} | |||
} | |||
~Pimpl() | |||
{ | |||
stopTimer(); | |||
[device release]; | |||
} | |||
void timerCallback() | |||
{ | |||
const DiskState state = getDiskState(); | |||
internal = (void*) dev; | |||
if (state != lastState) | |||
{ | |||
lastState = state; | |||
owner.sendChangeMessage (&owner); | |||
} | |||
} | |||
DiskState getDiskState() const | |||
{ | |||
if ([device->device isValid]) | |||
{ | |||
NSDictionary* status = [device->device status]; | |||
NSString* state = [status objectForKey: DRDeviceMediaStateKey]; | |||
if ([state isEqualTo: DRDeviceMediaStateNone]) | |||
{ | |||
if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue]) | |||
return trayOpen; | |||
return noDisc; | |||
} | |||
if ([state isEqualTo: DRDeviceMediaStateMediaPresent]) | |||
{ | |||
if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0) | |||
return writableDiskPresent; | |||
else | |||
return readOnlyDiskPresent; | |||
} | |||
} | |||
return unknown; | |||
} | |||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||
const Array<int> getAvailableWriteSpeeds() const | |||
{ | |||
Array<int> results; | |||
if ([device->device isValid]) | |||
{ | |||
NSArray* speeds = [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey]; | |||
for (unsigned int i = 0; i < [speeds count]; ++i) | |||
{ | |||
const int kbPerSec = [[speeds objectAtIndex: i] intValue]; | |||
results.add (kbPerSec / kilobytesPerSecond1x); | |||
} | |||
} | |||
return results; | |||
} | |||
bool setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
{ | |||
if ([device->device isValid]) | |||
{ | |||
device->underrunProtection = shouldBeEnabled; | |||
return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue]; | |||
} | |||
return false; | |||
} | |||
int getNumAvailableAudioBlocks() const | |||
{ | |||
return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey] | |||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
} | |||
OpenDiskDevice* device; | |||
private: | |||
DiskState lastState; | |||
AudioCDBurner& owner; | |||
}; | |||
//============================================================================== | |||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
{ | |||
pimpl = new Pimpl (*this, deviceIndex); | |||
} | |||
AudioCDBurner::~AudioCDBurner() | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
if (dev != 0) | |||
[dev release]; | |||
} | |||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||
{ | |||
ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||
if (b->internal == 0) | |||
if (b->pimpl->device == 0) | |||
b = 0; | |||
return b.release(); | |||
@@ -378,27 +470,56 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||
return s; | |||
} | |||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||
{ | |||
return pimpl->getDiskState(); | |||
} | |||
bool AudioCDBurner::isDiskPresent() const | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
return getDiskState() == writableDiskPresent; | |||
} | |||
return dev != 0 && [dev isDiskPresent]; | |||
bool AudioCDBurner::openTray() | |||
{ | |||
return pimpl->openTray(); | |||
} | |||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||
DiskState oldState = getDiskState(); | |||
DiskState newState = oldState; | |||
return [dev getNumAvailableAudioBlocks]; | |||
while (newState == oldState && Time::currentTimeMillis() < timeout) | |||
{ | |||
newState = getDiskState(); | |||
Thread::sleep (100); | |||
} | |||
return newState; | |||
} | |||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
const Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||
{ | |||
return pimpl->getAvailableWriteSpeeds(); | |||
} | |||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
{ | |||
return pimpl->setBufferUnderrunProtection (shouldBeEnabled); | |||
} | |||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
{ | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
return pimpl->getNumAvailableAudioBlocks(); | |||
} | |||
if (dev != 0) | |||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
{ | |||
if ([pimpl->device->device isValid]) | |||
{ | |||
[dev addSourceTrack: source numSamples: numSamps]; | |||
[pimpl->device addSourceTrack: source numSamples: numSamps]; | |||
return true; | |||
} | |||
@@ -406,32 +527,35 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
} | |||
const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener* listener, | |||
const bool ejectDiscAfterwards, | |||
const bool peformFakeBurnForTesting) | |||
bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, | |||
int writeSpeed) | |||
{ | |||
String error ("Couldn't open or write to the CD device"); | |||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||
if (dev != 0) | |||
if ([pimpl->device->device isValid]) | |||
{ | |||
error = String::empty; | |||
[dev burn: listener | |||
errorString: &error | |||
[pimpl->device burn: listener | |||
errorString: &error | |||
ejectAfterwards: ejectDiscAfterwards | |||
isFake: peformFakeBurnForTesting]; | |||
isFake: performFakeBurnForTesting | |||
speed: writeSpeed]; | |||
} | |||
return error; | |||
} | |||
#endif | |||
//============================================================================== | |||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDREADER | |||
void AudioCDReader::ejectDisk() | |||
{ | |||
const ScopedAutoReleasePool p; | |||
[[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; | |||
} | |||
#endif |
@@ -2141,28 +2141,24 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||
{ | |||
AudioCDBurner* b = new AudioCDBurner (deviceIndex); | |||
ScopedPointer<AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||
if (b->internal == 0) | |||
deleteAndZero (b); | |||
if (b->pimpl == 0) | |||
b = 0; | |||
return b; | |||
return b.release(); | |||
} | |||
class CDBurnerInfo : public IDiscMasterProgressEvents | |||
//============================================================================== | |||
class AudioCDBurner::Pimpl : public IDiscMasterProgressEvents | |||
{ | |||
public: | |||
CDBurnerInfo() | |||
: refCount (1), | |||
progress (0), | |||
shouldCancel (false), | |||
listener (0) | |||
Pimpl() | |||
: listener (0), progress (0), shouldCancel (false), refCount (1) | |||
{ | |||
} | |||
~CDBurnerInfo() | |||
{ | |||
} | |||
~Pimpl() {} | |||
HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result) | |||
{ | |||
@@ -2209,6 +2205,50 @@ public: | |||
HRESULT __stdcall NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||
HRESULT __stdcall NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||
int getIntProperty (const LPOLESTR name, const int defaultReturn) const | |||
{ | |||
ComSmartPtr<IPropertyStorage> prop; | |||
if (FAILED (discRecorder->GetRecorderProperties (&prop))) | |||
return defaultReturn; | |||
PROPSPEC iPropSpec; | |||
iPropSpec.ulKind = PRSPEC_LPWSTR; | |||
iPropSpec.lpwstr = name; | |||
PROPVARIANT iPropVariant; | |||
return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)) | |||
? defaultReturn : (int) iPropVariant.lVal; | |||
} | |||
bool setIntProperty (const LPOLESTR name, const int value) const | |||
{ | |||
ComSmartPtr<IPropertyStorage> prop; | |||
if (FAILED (discRecorder->GetRecorderProperties (&prop))) | |||
return false; | |||
PROPSPEC iPropSpec; | |||
iPropSpec.ulKind = PRSPEC_LPWSTR; | |||
iPropSpec.lpwstr = name; | |||
PROPVARIANT iPropVariant; | |||
if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))) | |||
return false; | |||
iPropVariant.lVal = (long) value; | |||
return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt)) | |||
&& SUCCEEDED (discRecorder->SetRecorderProperties (prop)); | |||
} | |||
class DiskOpener | |||
{ | |||
public: | |||
DiskOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); } | |||
~DiskOpener() { pimpl.discRecorder->Close(); } | |||
private: | |||
Pimpl& pimpl; | |||
}; | |||
IDiscMaster* discMaster; | |||
IDiscRecorder* discRecorder; | |||
IRedbookDiscMaster* redbook; | |||
@@ -2220,8 +2260,8 @@ private: | |||
int refCount; | |||
}; | |||
//============================================================================== | |||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
: internal (0) | |||
{ | |||
IDiscMaster* discMaster; | |||
IDiscRecorder* dr = enumCDBurners (0, deviceIndex, &discMaster); | |||
@@ -2230,69 +2270,115 @@ AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
{ | |||
IRedbookDiscMaster* redbook; | |||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | |||
hr = discMaster->SetActiveDiscRecorder (dr); | |||
CDBurnerInfo* const info = new CDBurnerInfo(); | |||
internal = info; | |||
info->discMaster = discMaster; | |||
info->discRecorder = dr; | |||
info->redbook = redbook; | |||
pimpl = new Pimpl(); | |||
pimpl->discMaster = discMaster; | |||
pimpl->discRecorder = dr; | |||
pimpl->redbook = redbook; | |||
} | |||
} | |||
AudioCDBurner::~AudioCDBurner() | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
if (info != 0) | |||
if (pimpl != 0) | |||
{ | |||
info->discRecorder->Close(); | |||
info->redbook->Release(); | |||
info->discRecorder->Release(); | |||
info->discMaster->Release(); | |||
pimpl->discRecorder->Close(); | |||
pimpl->redbook->Release(); | |||
pimpl->discRecorder->Release(); | |||
pimpl->discMaster->Release(); | |||
info->Release(); | |||
pimpl.release()->Release(); | |||
} | |||
} | |||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||
{ | |||
const Pimpl::DiskOpener opener (*pimpl); | |||
long type, flags; | |||
HRESULT hr = pimpl->discRecorder->QueryMediaType (&type, &flags); | |||
if (FAILED (hr)) | |||
return unknown; | |||
if (type != 0 && (flags & MEDIA_WRITABLE) != 0) | |||
return writableDiskPresent; | |||
if (type == 0) | |||
return noDisc; | |||
else | |||
return readOnlyDiskPresent; | |||
} | |||
bool AudioCDBurner::isDiskPresent() const | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
return getDiskState() == writableDiskPresent; | |||
} | |||
bool AudioCDBurner::openTray() | |||
{ | |||
const Pimpl::DiskOpener opener (*pimpl); | |||
return pimpl->discRecorder->Eject() == S_OK; | |||
} | |||
HRESULT hr = info->discRecorder->OpenExclusive(); | |||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||
{ | |||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||
DiskState oldState = getDiskState(); | |||
DiskState newState = oldState; | |||
long type, flags; | |||
hr = info->discRecorder->QueryMediaType (&type, &flags); | |||
while (newState == oldState && Time::currentTimeMillis() < timeout) | |||
{ | |||
newState = getDiskState(); | |||
Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis()))); | |||
} | |||
info->discRecorder->Close(); | |||
return hr == S_OK && type != 0 && (flags & MEDIA_WRITABLE) != 0; | |||
return newState; | |||
} | |||
const Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||
{ | |||
Array<int> results; | |||
const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1); | |||
const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 }; | |||
for (int i = 0; i < numElementsInArray (speeds); ++i) | |||
if (speeds[i] <= maxSpeed) | |||
results.add (speeds[i]); | |||
results.addIfNotAlreadyThere (maxSpeed); | |||
return results; | |||
} | |||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
{ | |||
if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0) | |||
return false; | |||
pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0); | |||
return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0; | |||
} | |||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
long blocksFree = 0; | |||
info->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||
return blocksFree; | |||
} | |||
const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||
const bool ejectDiscAfterwards, | |||
const bool performFakeBurnForTesting) | |||
const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, int writeSpeed) | |||
{ | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
info->listener = listener; | |||
info->progress = 0; | |||
info->shouldCancel = false; | |||
pimpl->listener = listener; | |||
pimpl->progress = 0; | |||
pimpl->shouldCancel = false; | |||
UINT_PTR cookie; | |||
HRESULT hr = info->discMaster->ProgressAdvise (info, &cookie); | |||
HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl, &cookie); | |||
hr = info->discMaster->RecordDisc (performFakeBurnForTesting, | |||
ejectDiscAfterwards); | |||
hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting, | |||
ejectDiscAfterwards); | |||
String error; | |||
if (hr != S_OK) | |||
@@ -2307,29 +2393,28 @@ const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||
error = e; | |||
} | |||
info->discMaster->ProgressUnadvise (cookie); | |||
info->listener = 0; | |||
pimpl->discMaster->ProgressUnadvise (cookie); | |||
pimpl->listener = 0; | |||
return error; | |||
} | |||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||
{ | |||
if (source == 0) | |||
if (audioSource == 0) | |||
return false; | |||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||
ScopedPointer<AudioSource> source (audioSource); | |||
long bytesPerBlock; | |||
HRESULT hr = info->redbook->GetAudioBlockSize (&bytesPerBlock); | |||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock); | |||
const int samplesPerBlock = bytesPerBlock / 4; | |||
bool ok = true; | |||
hr = info->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
HeapBlock <byte> buffer (bytesPerBlock); | |||
AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | |||
int samplesDone = 0; | |||
@@ -2355,9 +2440,9 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||
AudioDataConverters::convertFloatToInt16LE (sourceBuffer.getSampleData (1, 0), | |||
buffer + 2, samplesPerBlock, 4); | |||
hr = info->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||
hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||
if (hr != S_OK) | |||
if (FAILED (hr)) | |||
ok = false; | |||
samplesDone += samplesPerBlock; | |||
@@ -2366,10 +2451,7 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||
break; | |||
} | |||
hr = info->redbook->CloseAudioTrack(); | |||
delete source; | |||
hr = pimpl->redbook->CloseAudioTrack(); | |||
return ok && hr == S_OK; | |||
} | |||