|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.
   JUCE is an open source library subject to commercial or open-source
   licensing.
   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).
   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy
   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).
   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.
  ==============================================================================
*/
namespace juce
{
namespace CDBurnerHelpers
{
    IDiscRecorder* enumCDBurners (StringArray* list, int indexToOpen, IDiscMaster** master)
    {
        CoInitialize (0);
        IDiscMaster* dm;
        IDiscRecorder* result = nullptr;
        if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj, 0,
                                         CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
                                         IID_IDiscMaster,
                                         (void**) &dm)))
        {
            if (SUCCEEDED (dm->Open()))
            {
                IEnumDiscRecorders* drEnum = nullptr;
                if (SUCCEEDED (dm->EnumDiscRecorders (&drEnum)))
                {
                    IDiscRecorder* dr = nullptr;
                    DWORD dummy;
                    int index = 0;
                    while (drEnum->Next (1, &dr, &dummy) == S_OK)
                    {
                        if (indexToOpen == index)
                        {
                            result = dr;
                            break;
                        }
                        else if (list != nullptr)
                        {
                            BSTR path;
                            if (SUCCEEDED (dr->GetPath (&path)))
                                list->add ((const WCHAR*) path);
                        }
                        ++index;
                        dr->Release();
                    }
                    drEnum->Release();
                }
                if (master == 0)
                    dm->Close();
            }
            if (master != nullptr)
                *master = dm;
            else
                dm->Release();
        }
        return result;
    }
}
//==============================================================================
class AudioCDBurner::Pimpl  : public ComBaseClassHelper <IDiscMasterProgressEvents>,
                              public Timer
{
public:
    Pimpl (AudioCDBurner& owner_, IDiscMaster* discMaster_, IDiscRecorder* discRecorder_)
      : owner (owner_), discMaster (discMaster_), discRecorder (discRecorder_), redbook (0),
        listener (0), progress (0), shouldCancel (false)
    {
        HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook);
        jassert (SUCCEEDED (hr));
        hr = discMaster->SetActiveDiscRecorder (discRecorder);
        //jassert (SUCCEEDED (hr));
        lastState = getDiskState();
        startTimer (2000);
    }
    ~Pimpl()  {}
    void releaseObjects()
    {
        discRecorder->Close();
        if (redbook != nullptr)
            redbook->Release();
        discRecorder->Release();
        discMaster->Release();
        Release();
    }
    JUCE_COMRESULT QueryCancel (boolean* pbCancel)
    {
        if (listener != nullptr && ! shouldCancel)
            shouldCancel = listener->audioCDBurnProgress (progress);
        *pbCancel = shouldCancel;
        return S_OK;
    }
    JUCE_COMRESULT NotifyBlockProgress (long nCompleted, long nTotal)
    {
        progress = nCompleted / (float) nTotal;
        shouldCancel = listener != nullptr && listener->audioCDBurnProgress (progress);
        return E_NOTIMPL;
    }
    JUCE_COMRESULT NotifyPnPActivity (void)                              { return E_NOTIMPL; }
    JUCE_COMRESULT NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/)    { return E_NOTIMPL; }
    JUCE_COMRESULT NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/)   { return E_NOTIMPL; }
    JUCE_COMRESULT NotifyPreparingBurn (long /*nEstimatedSeconds*/)      { return E_NOTIMPL; }
    JUCE_COMRESULT NotifyClosingDisc (long /*nEstimatedSeconds*/)        { return E_NOTIMPL; }
    JUCE_COMRESULT NotifyBurnComplete (HRESULT /*status*/)               { return E_NOTIMPL; }
    JUCE_COMRESULT NotifyEraseComplete (HRESULT /*status*/)              { return E_NOTIMPL; }
    class ScopedDiscOpener
    {
    public:
        ScopedDiscOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); }
        ~ScopedDiscOpener()                     { pimpl.discRecorder->Close(); }
    private:
        Pimpl& pimpl;
        JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener)
    };
    DiskState getDiskState()
    {
        const ScopedDiscOpener opener (*this);
        long type, flags;
        HRESULT hr = discRecorder->QueryMediaType (&type, &flags);
        if (FAILED (hr))
            return unknown;
        if (type != 0 && (flags & MEDIA_WRITABLE) != 0)
            return writableDiskPresent;
        if (type == 0)
            return noDisc;
        return readOnlyDiskPresent;
    }
    int getIntProperty (const LPOLESTR name, const int defaultReturn) const
    {
        ComSmartPtr<IPropertyStorage> prop;
        if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
            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.resetAndGetPointerAddress())))
            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));
    }
    void timerCallback() override
    {
        const DiskState state = getDiskState();
        if (state != lastState)
        {
            lastState = state;
            owner.sendChangeMessage();
        }
    }
    AudioCDBurner& owner;
    DiskState lastState;
    IDiscMaster* discMaster;
    IDiscRecorder* discRecorder;
    IRedbookDiscMaster* redbook;
    AudioCDBurner::BurnProgressListener* listener;
    float progress;
    bool shouldCancel;
};
//==============================================================================
AudioCDBurner::AudioCDBurner (const int deviceIndex)
{
    IDiscMaster* discMaster = nullptr;
    IDiscRecorder* discRecorder = CDBurnerHelpers::enumCDBurners (0, deviceIndex, &discMaster);
    if (discRecorder != nullptr)
        pimpl.reset (new Pimpl (*this, discMaster, discRecorder));
}
AudioCDBurner::~AudioCDBurner()
{
    if (pimpl != nullptr)
        pimpl.release()->releaseObjects();
}
StringArray AudioCDBurner::findAvailableDevices()
{
    StringArray devs;
    CDBurnerHelpers::enumCDBurners (&devs, -1, 0);
    return devs;
}
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
{
    std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
    if (b->pimpl == 0)
        b = nullptr;
    return b.release();
}
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
{
    return pimpl->getDiskState();
}
bool AudioCDBurner::isDiskPresent() const
{
    return getDiskState() == writableDiskPresent;
}
bool AudioCDBurner::openTray()
{
    const Pimpl::ScopedDiscOpener opener (*pimpl);
    return SUCCEEDED (pimpl->discRecorder->Eject());
}
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;
}
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
{
    long blocksFree = 0;
    pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree);
    return blocksFree;
}
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards,
                            bool performFakeBurnForTesting, int writeSpeed)
{
    pimpl->setIntProperty (L"WriteSpeed", writeSpeed > 0 ? writeSpeed : -1);
    pimpl->listener = listener;
    pimpl->progress = 0;
    pimpl->shouldCancel = false;
    UINT_PTR cookie;
    HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl.get(), &cookie);
    hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting,
                                        ejectDiscAfterwards);
    String error;
    if (hr != S_OK)
    {
        const char* e = "Couldn't open or write to the CD device";
        if (hr == IMAPI_E_USERABORT)
            e = "User cancelled the write operation";
        else if (hr == IMAPI_E_MEDIUM_NOTPRESENT || hr == IMAPI_E_TRACKOPEN)
            e = "No Disk present";
        error = e;
    }
    pimpl->discMaster->ProgressUnadvise (cookie);
    pimpl->listener = 0;
    return error;
}
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples)
{
    if (audioSource == 0)
        return false;
    std::unique_ptr<AudioSource> source (audioSource);
    long bytesPerBlock;
    HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock);
    const int samplesPerBlock = bytesPerBlock / 4;
    bool ok = true;
    hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4));
    HeapBlock<byte> buffer (bytesPerBlock);
    AudioBuffer<float> sourceBuffer (2, samplesPerBlock);
    int samplesDone = 0;
    source->prepareToPlay (samplesPerBlock, 44100.0);
    while (ok)
    {
        {
            AudioSourceChannelInfo info (&sourceBuffer, 0, samplesPerBlock);
            sourceBuffer.clear();
            source->getNextAudioBlock (info);
        }
        buffer.clear (bytesPerBlock);
        typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian,
                                    AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
        typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian,
                                    AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
        CDSampleFormat left (buffer, 2);
        left.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (0)), samplesPerBlock);
        CDSampleFormat right (buffer + 2, 2);
        right.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (1)), samplesPerBlock);
        hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock);
        if (FAILED (hr))
            ok = false;
        samplesDone += samplesPerBlock;
        if (samplesDone >= numSamples)
            break;
    }
    hr = pimpl->redbook->CloseAudioTrack();
    return ok && hr == S_OK;
}
} // namespace juce
 |