@@ -295,7 +295,7 @@ void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const | |||||
const String name (resources[i]->name); | const String name (resources[i]->name); | ||||
const MemoryBlock& mb = resources[i]->data; | const MemoryBlock& mb = resources[i]->data; | ||||
defs << "// JUCER_RESOURCE: " << name << ", " << mb.getSize() | |||||
defs << "// JUCER_RESOURCE: " << name << ", " << (int) mb.getSize() | |||||
<< ", \"" | << ", \"" | ||||
<< File (resources[i]->originalFilename) | << File (resources[i]->originalFilename) | ||||
.getRelativePathFrom (code.document->getFile()) | .getRelativePathFrom (code.document->getFile()) | ||||
@@ -311,7 +311,7 @@ void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const | |||||
MemoryOutputStream out (65536, 16384); | MemoryOutputStream out (65536, 16384); | ||||
int charsOnLine = line1.length(); | 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]); | const int num = ((int) (unsigned char) mb[j]); | ||||
out << num << ','; | out << num << ','; | ||||
@@ -338,7 +338,7 @@ void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const | |||||
<< " = (const char*) resource_" << code.className << "_" << name | << " = (const char*) resource_" << code.className << "_" << name | ||||
<< ";\nconst int " | << ";\nconst int " | ||||
<< code.className << "::" << name << "Size = " | << code.className << "::" << name << "Size = " | ||||
<< mb.getSize() | |||||
<< (int) mb.getSize() | |||||
<< ";\n\n"; | << ";\n\n"; | ||||
} | } | ||||
@@ -5902,10 +5902,14 @@ static const String parseAbsolutePath (String path) | |||||
{ | { | ||||
if (path[1] != File::separator) | 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; | path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; | ||||
} | } | ||||
@@ -5915,10 +5919,14 @@ static const String parseAbsolutePath (String path) | |||||
if (path.isEmpty()) | if (path.isEmpty()) | ||||
return String::empty; | 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(); | return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); | ||||
} | } | ||||
@@ -5957,16 +5965,14 @@ static const String parseAbsolutePath (String path) | |||||
} | } | ||||
else if (! path.startsWithChar (File::separator)) | 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(); | return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); | ||||
} | } | ||||
@@ -21866,6 +21872,14 @@ struct SMPLChunk | |||||
} | } | ||||
} PACKED; | } PACKED; | ||||
struct ExtensibleWavSubFormat | |||||
{ | |||||
uint32 data1; | |||||
uint16 data2; | |||||
uint16 data3; | |||||
uint8 data4[8]; | |||||
} PACKED; | |||||
#if JUCE_MSVC | #if JUCE_MSVC | ||||
#pragma pack (pop) | #pragma pack (pop) | ||||
#endif | #endif | ||||
@@ -21910,7 +21924,7 @@ public: | |||||
if (chunkType == chunkName ("fmt ")) | if (chunkType == chunkName ("fmt ")) | ||||
{ | { | ||||
// read the format chunk | // read the format chunk | ||||
const short format = input->readShort(); | |||||
const unsigned short format = input->readShort(); | |||||
const short numChans = input->readShort(); | const short numChans = input->readShort(); | ||||
sampleRate = input->readInt(); | sampleRate = input->readInt(); | ||||
const int bytesPerSec = input->readInt(); | const int bytesPerSec = input->readInt(); | ||||
@@ -21920,9 +21934,41 @@ public: | |||||
bitsPerSample = 8 * bytesPerFrame / numChans; | bitsPerSample = 8 * bytesPerFrame / numChans; | ||||
if (format == 3) | if (format == 3) | ||||
{ | |||||
usesFloatingPointData = true; | 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) | else if (format != 1) | ||||
{ | |||||
bytesPerFrame = 0; | bytesPerFrame = 0; | ||||
} | |||||
hasGotType = true; | hasGotType = true; | ||||
} | } | ||||
@@ -221857,28 +221903,23 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | 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: | 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) | 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 NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; } | ||||
HRESULT __stdcall NotifyEraseComplete (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; | IDiscMaster* discMaster; | ||||
IDiscRecorder* discRecorder; | IDiscRecorder* discRecorder; | ||||
IRedbookDiscMaster* redbook; | IRedbookDiscMaster* redbook; | ||||
@@ -221937,7 +222022,6 @@ private: | |||||
}; | }; | ||||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | AudioCDBurner::AudioCDBurner (const int deviceIndex) | ||||
: internal (0) | |||||
{ | { | ||||
IDiscMaster* discMaster; | IDiscMaster* discMaster; | ||||
IDiscRecorder* dr = enumCDBurners (0, deviceIndex, &discMaster); | IDiscRecorder* dr = enumCDBurners (0, deviceIndex, &discMaster); | ||||
@@ -221946,69 +222030,115 @@ AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||||
{ | { | ||||
IRedbookDiscMaster* redbook; | IRedbookDiscMaster* redbook; | ||||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | ||||
hr = discMaster->SetActiveDiscRecorder (dr); | 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() | 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 | 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 | int AudioCDBurner::getNumAvailableAudioBlocks() const | ||||
{ | { | ||||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||||
long blocksFree = 0; | long blocksFree = 0; | ||||
info->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||||
return 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; | 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; | String error; | ||||
if (hr != S_OK) | if (hr != S_OK) | ||||
@@ -222023,29 +222153,28 @@ const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||||
error = e; | error = e; | ||||
} | } | ||||
info->discMaster->ProgressUnadvise (cookie); | |||||
info->listener = 0; | |||||
pimpl->discMaster->ProgressUnadvise (cookie); | |||||
pimpl->listener = 0; | |||||
return error; | return error; | ||||
} | } | ||||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||||
{ | { | ||||
if (source == 0) | |||||
if (audioSource == 0) | |||||
return false; | return false; | ||||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||||
ScopedPointer<AudioSource> source (audioSource); | |||||
long bytesPerBlock; | long bytesPerBlock; | ||||
HRESULT hr = info->redbook->GetAudioBlockSize (&bytesPerBlock); | |||||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock); | |||||
const int samplesPerBlock = bytesPerBlock / 4; | const int samplesPerBlock = bytesPerBlock / 4; | ||||
bool ok = true; | bool ok = true; | ||||
hr = info->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||||
HeapBlock <byte> buffer (bytesPerBlock); | HeapBlock <byte> buffer (bytesPerBlock); | ||||
AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | ||||
int samplesDone = 0; | int samplesDone = 0; | ||||
@@ -222071,9 +222200,9 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||||
AudioDataConverters::convertFloatToInt16LE (sourceBuffer.getSampleData (1, 0), | AudioDataConverters::convertFloatToInt16LE (sourceBuffer.getSampleData (1, 0), | ||||
buffer + 2, samplesPerBlock, 4); | 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; | ok = false; | ||||
samplesDone += samplesPerBlock; | samplesDone += samplesPerBlock; | ||||
@@ -222082,10 +222211,7 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||||
break; | break; | ||||
} | } | ||||
hr = info->redbook->CloseAudioTrack(); | |||||
delete source; | |||||
hr = pimpl->redbook->CloseAudioTrack(); | |||||
return ok && hr == S_OK; | return ok && hr == S_OK; | ||||
} | } | ||||
@@ -228305,6 +228431,29 @@ void JUCE_CALLTYPE Thread::sleep (int millisecs) | |||||
const tchar File::separator = T('/'); | const tchar File::separator = T('/'); | ||||
const tchar* File::separatorString = 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); | bool juce_copyFile (const String& s, const String& d); | ||||
static bool juce_stat (const String& fileName, struct stat& info) | static bool juce_stat (const String& fileName, struct stat& info) | ||||
@@ -228455,7 +228604,7 @@ const File juce_getExecutableFile() | |||||
{ | { | ||||
Dl_info exeInfo; | Dl_info exeInfo; | ||||
dladdr ((const void*) juce_getExecutableFile, &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.. | // 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; | 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 | const String File::getVersion() const | ||||
{ | { | ||||
return String::empty; // xxx not yet implemented | 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::separator = T('/'); | ||||
const tchar* File::separatorString = 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); | bool juce_copyFile (const String& s, const String& d); | ||||
static bool juce_stat (const String& fileName, struct stat& info) | static bool juce_stat (const String& fileName, struct stat& info) | ||||
@@ -238250,7 +238411,7 @@ const File juce_getExecutableFile() | |||||
{ | { | ||||
Dl_info exeInfo; | Dl_info exeInfo; | ||||
dladdr ((const void*) juce_getExecutableFile, &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.. | // 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; | 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 String File::getVersion() const | ||||
{ | { | ||||
const ScopedAutoReleasePool pool; | const ScopedAutoReleasePool pool; | ||||
@@ -248313,24 +248461,25 @@ bool juce_OpenQuickTimeMovieFromStream (InputStream* movieStream, Movie& result, | |||||
// compiled on its own). | // compiled on its own). | ||||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDBURNER | #if JUCE_INCLUDED_FILE && JUCE_USE_CDBURNER | ||||
const int kilobytesPerSecond1x = 176; | |||||
END_JUCE_NAMESPACE | END_JUCE_NAMESPACE | ||||
#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | #define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | ||||
@interface OpenDiskDevice : NSObject | @interface OpenDiskDevice : NSObject | ||||
{ | { | ||||
@public | |||||
DRDevice* device; | DRDevice* device; | ||||
NSMutableArray* tracks; | NSMutableArray* tracks; | ||||
bool underrunProtection; | |||||
} | } | ||||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device; | |||||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device; | |||||
- (void) dealloc; | - (void) dealloc; | ||||
- (bool) isDiskPresent; | |||||
- (int) getNumAvailableAudioBlocks; | |||||
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; | - (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; | ||||
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error | - (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 | @end | ||||
#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer) | #define AudioTrackProducer MakeObjCClassName(AudioTrackProducer) | ||||
@@ -248370,12 +248519,13 @@ END_JUCE_NAMESPACE | |||||
@implementation OpenDiskDevice | @implementation OpenDiskDevice | ||||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device_ | |||||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ | |||||
{ | { | ||||
[super init]; | [super init]; | ||||
device = device_; | device = device_; | ||||
tracks = [[NSMutableArray alloc] init]; | tracks = [[NSMutableArray alloc] init]; | ||||
underrunProtection = true; | |||||
return self; | return self; | ||||
} | } | ||||
@@ -248385,19 +248535,6 @@ END_JUCE_NAMESPACE | |||||
[super dealloc]; | [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_ | - (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) numSamples_ | ||||
{ | { | ||||
AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: 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 | - (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]; | DRBurn* burn = [DRBurn burnForDevice: device]; | ||||
@@ -248427,8 +248564,14 @@ END_JUCE_NAMESPACE | |||||
[d autorelease]; | [d autorelease]; | ||||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | ||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | [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 setProperties: d]; | ||||
[burn writeLayout: tracks]; | [burn writeLayout: tracks]; | ||||
@@ -248599,27 +248742,123 @@ END_JUCE_NAMESPACE | |||||
BEGIN_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() | AudioCDBurner::~AudioCDBurner() | ||||
{ | { | ||||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||||
if (dev != 0) | |||||
[dev release]; | |||||
} | } | ||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | ||||
{ | { | ||||
ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | ||||
if (b->internal == 0) | |||||
if (b->pimpl->device == 0) | |||||
b = 0; | b = 0; | ||||
return b.release(); | return b.release(); | ||||
@@ -248657,27 +248896,56 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||||
return s; | return s; | ||||
} | } | ||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||||
{ | |||||
return pimpl->getDiskState(); | |||||
} | |||||
bool AudioCDBurner::isDiskPresent() const | 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; | return true; | ||||
} | } | ||||
@@ -248685,25 +248953,30 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||||
} | } | ||||
const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener* listener, | 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"); | 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; | error = String::empty; | ||||
[dev burn: listener | |||||
errorString: &error | |||||
[pimpl->device burn: listener | |||||
errorString: &error | |||||
ejectAfterwards: ejectDiscAfterwards | ejectAfterwards: ejectDiscAfterwards | ||||
isFake: peformFakeBurnForTesting]; | |||||
isFake: performFakeBurnForTesting | |||||
speed: writeSpeed]; | |||||
} | } | ||||
return error; | return error; | ||||
} | } | ||||
#endif | |||||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDREADER | |||||
void AudioCDReader::ejectDisk() | void AudioCDReader::ejectDisk() | ||||
{ | { | ||||
const ScopedAutoReleasePool p; | const ScopedAutoReleasePool p; | ||||
@@ -4377,7 +4377,7 @@ public: | |||||
static const String descriptionOfSizeInBytes (const int64 bytes); | static const String descriptionOfSizeInBytes (const int64 bytes); | ||||
const String& getFullPathName() const { return fullPath; } | |||||
const String& getFullPathName() const throw() { return fullPath; } | |||||
const String getFileName() const; | const String getFileName() const; | ||||
@@ -14016,7 +14016,7 @@ public: | |||||
#if JUCE_USE_CDBURNER | #if JUCE_USE_CDBURNER | ||||
class AudioCDBurner | |||||
class AudioCDBurner : public ChangeBroadcaster | |||||
{ | { | ||||
public: | public: | ||||
@@ -14026,8 +14026,28 @@ public: | |||||
~AudioCDBurner(); | ~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 isDiskPresent() const; | ||||
bool openTray(); | |||||
DiskState waitUntilStateChange (int timeOutMilliseconds); | |||||
const Array<int> getAvailableWriteSpeeds() const; | |||||
bool setBufferUnderrunProtection (const bool shouldBeEnabled); | |||||
int getNumAvailableAudioBlocks() const; | int getNumAvailableAudioBlocks() const; | ||||
bool addAudioTrack (AudioSource* source, int numSamples); | bool addAudioTrack (AudioSource* source, int numSamples); | ||||
@@ -14042,15 +14062,20 @@ public: | |||||
}; | }; | ||||
const String burn (BurnProgressListener* listener, | const String burn (BurnProgressListener* listener, | ||||
const bool ejectDiscAfterwards, | |||||
const bool peformFakeBurnForTesting); | |||||
bool ejectDiscAfterwards, | |||||
bool performFakeBurnForTesting, | |||||
int writeSpeed); | |||||
void abortBurn(); | |||||
juce_UseDebuggingNewOperator | juce_UseDebuggingNewOperator | ||||
private: | private: | ||||
AudioCDBurner (const int deviceIndex); | AudioCDBurner (const int deviceIndex); | ||||
void* internal; | |||||
class Pimpl; | |||||
friend class ScopedPointer<Pimpl>; | |||||
ScopedPointer<Pimpl> pimpl; | |||||
}; | }; | ||||
#endif | #endif | ||||
@@ -35,7 +35,7 @@ | |||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
*/ | */ | ||||
class AudioCDBurner | |||||
class AudioCDBurner : public ChangeBroadcaster | |||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
@@ -55,10 +55,53 @@ public: | |||||
~AudioCDBurner(); | ~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; | 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. | /** Returns the number of free blocks on the disk. | ||||
There are 75 blocks per second, at 44100Hz. | There are 75 blocks per second, at 44100Hz. | ||||
@@ -75,9 +118,9 @@ public: | |||||
*/ | */ | ||||
bool addAudioTrack (AudioSource* source, int numSamples); | 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 | class BurnProgressListener | ||||
{ | { | ||||
@@ -87,14 +130,32 @@ public: | |||||
/** Called at intervals to report on the progress of the AudioCDBurner. | /** 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; | 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 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 | juce_UseDebuggingNewOperator | ||||
@@ -102,7 +163,9 @@ public: | |||||
private: | private: | ||||
AudioCDBurner (const int deviceIndex); | 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 | #if JUCE_INCLUDED_FILE && JUCE_USE_CDBURNER | ||||
//============================================================================== | //============================================================================== | ||||
const int kilobytesPerSecond1x = 176; | |||||
END_JUCE_NAMESPACE | END_JUCE_NAMESPACE | ||||
#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | #define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | ||||
@interface OpenDiskDevice : NSObject | @interface OpenDiskDevice : NSObject | ||||
{ | { | ||||
@public | |||||
DRDevice* device; | DRDevice* device; | ||||
NSMutableArray* tracks; | NSMutableArray* tracks; | ||||
bool underrunProtection; | |||||
} | } | ||||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device; | |||||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device; | |||||
- (void) dealloc; | - (void) dealloc; | ||||
- (bool) isDiskPresent; | |||||
- (int) getNumAvailableAudioBlocks; | |||||
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; | - (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; | ||||
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error | - (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 | @end | ||||
//============================================================================== | //============================================================================== | ||||
@@ -87,12 +88,13 @@ END_JUCE_NAMESPACE | |||||
//============================================================================== | //============================================================================== | ||||
@implementation OpenDiskDevice | @implementation OpenDiskDevice | ||||
- (OpenDiskDevice*) initWithDevice: (DRDevice*) device_ | |||||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ | |||||
{ | { | ||||
[super init]; | [super init]; | ||||
device = device_; | device = device_; | ||||
tracks = [[NSMutableArray alloc] init]; | tracks = [[NSMutableArray alloc] init]; | ||||
underrunProtection = true; | |||||
return self; | return self; | ||||
} | } | ||||
@@ -102,19 +104,6 @@ END_JUCE_NAMESPACE | |||||
[super dealloc]; | [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_ | - (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) numSamples_ | ||||
{ | { | ||||
AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: 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 | - (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]; | DRBurn* burn = [DRBurn burnForDevice: device]; | ||||
@@ -144,8 +133,14 @@ END_JUCE_NAMESPACE | |||||
[d autorelease]; | [d autorelease]; | ||||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | ||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | [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 setProperties: d]; | ||||
[burn writeLayout: tracks]; | [burn writeLayout: tracks]; | ||||
@@ -320,27 +315,124 @@ END_JUCE_NAMESPACE | |||||
BEGIN_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() | AudioCDBurner::~AudioCDBurner() | ||||
{ | { | ||||
OpenDiskDevice* dev = (OpenDiskDevice*) internal; | |||||
if (dev != 0) | |||||
[dev release]; | |||||
} | } | ||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | ||||
{ | { | ||||
ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | ||||
if (b->internal == 0) | |||||
if (b->pimpl->device == 0) | |||||
b = 0; | b = 0; | ||||
return b.release(); | return b.release(); | ||||
@@ -378,27 +470,56 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||||
return s; | return s; | ||||
} | } | ||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||||
{ | |||||
return pimpl->getDiskState(); | |||||
} | |||||
bool AudioCDBurner::isDiskPresent() const | 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; | return true; | ||||
} | } | ||||
@@ -406,32 +527,35 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||||
} | } | ||||
const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener* listener, | 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"); | 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; | error = String::empty; | ||||
[dev burn: listener | |||||
errorString: &error | |||||
[pimpl->device burn: listener | |||||
errorString: &error | |||||
ejectAfterwards: ejectDiscAfterwards | ejectAfterwards: ejectDiscAfterwards | ||||
isFake: peformFakeBurnForTesting]; | |||||
isFake: performFakeBurnForTesting | |||||
speed: writeSpeed]; | |||||
} | } | ||||
return error; | return error; | ||||
} | } | ||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_INCLUDED_FILE && JUCE_USE_CDREADER | |||||
void AudioCDReader::ejectDisk() | void AudioCDReader::ejectDisk() | ||||
{ | { | ||||
const ScopedAutoReleasePool p; | const ScopedAutoReleasePool p; | ||||
[[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; | [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; | ||||
} | } | ||||
#endif | #endif |
@@ -2141,28 +2141,24 @@ const StringArray AudioCDBurner::findAvailableDevices() | |||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | 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: | 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) | 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 NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; } | ||||
HRESULT __stdcall NotifyEraseComplete (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; | IDiscMaster* discMaster; | ||||
IDiscRecorder* discRecorder; | IDiscRecorder* discRecorder; | ||||
IRedbookDiscMaster* redbook; | IRedbookDiscMaster* redbook; | ||||
@@ -2220,8 +2260,8 @@ private: | |||||
int refCount; | int refCount; | ||||
}; | }; | ||||
//============================================================================== | |||||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | AudioCDBurner::AudioCDBurner (const int deviceIndex) | ||||
: internal (0) | |||||
{ | { | ||||
IDiscMaster* discMaster; | IDiscMaster* discMaster; | ||||
IDiscRecorder* dr = enumCDBurners (0, deviceIndex, &discMaster); | IDiscRecorder* dr = enumCDBurners (0, deviceIndex, &discMaster); | ||||
@@ -2230,69 +2270,115 @@ AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||||
{ | { | ||||
IRedbookDiscMaster* redbook; | IRedbookDiscMaster* redbook; | ||||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | ||||
hr = discMaster->SetActiveDiscRecorder (dr); | 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() | 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 | 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 | int AudioCDBurner::getNumAvailableAudioBlocks() const | ||||
{ | { | ||||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||||
long blocksFree = 0; | long blocksFree = 0; | ||||
info->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||||
return 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; | 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; | String error; | ||||
if (hr != S_OK) | if (hr != S_OK) | ||||
@@ -2307,29 +2393,28 @@ const String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||||
error = e; | error = e; | ||||
} | } | ||||
info->discMaster->ProgressUnadvise (cookie); | |||||
info->listener = 0; | |||||
pimpl->discMaster->ProgressUnadvise (cookie); | |||||
pimpl->listener = 0; | |||||
return error; | return error; | ||||
} | } | ||||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||||
{ | { | ||||
if (source == 0) | |||||
if (audioSource == 0) | |||||
return false; | return false; | ||||
CDBurnerInfo* const info = (CDBurnerInfo*) internal; | |||||
ScopedPointer<AudioSource> source (audioSource); | |||||
long bytesPerBlock; | long bytesPerBlock; | ||||
HRESULT hr = info->redbook->GetAudioBlockSize (&bytesPerBlock); | |||||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock); | |||||
const int samplesPerBlock = bytesPerBlock / 4; | const int samplesPerBlock = bytesPerBlock / 4; | ||||
bool ok = true; | bool ok = true; | ||||
hr = info->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||||
HeapBlock <byte> buffer (bytesPerBlock); | HeapBlock <byte> buffer (bytesPerBlock); | ||||
AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | ||||
int samplesDone = 0; | int samplesDone = 0; | ||||
@@ -2355,9 +2440,9 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||||
AudioDataConverters::convertFloatToInt16LE (sourceBuffer.getSampleData (1, 0), | AudioDataConverters::convertFloatToInt16LE (sourceBuffer.getSampleData (1, 0), | ||||
buffer + 2, samplesPerBlock, 4); | 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; | ok = false; | ||||
samplesDone += samplesPerBlock; | samplesDone += samplesPerBlock; | ||||
@@ -2366,10 +2451,7 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamples) | |||||
break; | break; | ||||
} | } | ||||
hr = info->redbook->CloseAudioTrack(); | |||||
delete source; | |||||
hr = pimpl->redbook->CloseAudioTrack(); | |||||
return ok && hr == S_OK; | return ok && hr == S_OK; | ||||
} | } | ||||