audacia/src/import/ImportAUP.cpp

1570 lines
38 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ImportAUP.cpp
*//****************************************************************//**
\class AUPmportFileHandle
\brief An ImportFileHandle for AUP files (pre-AUP3)
*//****************************************************************//**
\class AUPImportPlugin
\brief An ImportPlugin for AUP files (pre-AUP3)
*//*******************************************************************/
#include "../Audacity.h" // for USE_* macros
#include "Import.h"
#include "ImportPlugin.h"
#include "../Envelope.h"
#include "../FileFormats.h"
#include "../FileNames.h"
#include "../LabelTrack.h"
#if defined(USE_MIDI)
#include "../NoteTrack.h"
#endif
#include "../Prefs.h"
#include "../Project.h"
#include "../ProjectFileIO.h"
#include "../ProjectFileIORegistry.h"
#include "../ProjectFileManager.h"
#include "../ProjectHistory.h"
#include "../ProjectSelectionManager.h"
#include "../ProjectSettings.h"
#include "../Tags.h"
#include "../TimeTrack.h"
#include "../ViewInfo.h"
#include "../WaveClip.h"
#include "../WaveTrack.h"
#include "../toolbars/SelectionBar.h"
#include "../widgets/AudacityMessageBox.h"
#include "../widgets/NumericTextCtrl.h"
#include "../widgets/ProgressDialog.h"
#include "../xml/XMLFileReader.h"
#include <map>
#define DESC XO("AUP project files")
static const auto exts = {wxT("aup")};
#include <wx/dir.h>
#include <wx/ffile.h>
#include <wx/file.h>
#include <wx/frame.h>
#include <wx/string.h>
#include <wx/utils.h>
class AUPImportFileHandle;
using ImportHandle = std::unique_ptr<ImportFileHandle>;
using NewChannelGroup = std::vector<std::shared_ptr<WaveTrack>>;
class AUPImportPlugin final : public ImportPlugin
{
public:
AUPImportPlugin();
~AUPImportPlugin();
wxString GetPluginStringID() override;
TranslatableString GetPluginFormatDescription() override;
ImportHandle Open(const FilePath &fileName,
AudacityProject *project) override;
};
class AUPImportFileHandle final : public ImportFileHandle,
public XMLTagHandler
{
public:
AUPImportFileHandle(const FilePath &name,
AudacityProject *project);
~AUPImportFileHandle();
TranslatableString GetFileDescription() override;
ByteCount GetFileUncompressedBytes() override;
ProgressResult Import(TrackFactory *trackFactory,
TrackHolders &outTracks,
Tags *tags) override;
wxInt32 GetStreamCount() override;
const TranslatableStrings &GetStreamInfo() override;
void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override;
bool Open();
private:
struct node
{
wxString parent;
wxString tag;
XMLTagHandler *handler;
};
using stack = std::vector<struct node>;
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
void HandleXMLEndTag(const wxChar *tag) override;
XMLTagHandler *HandleXMLChild(const wxChar *tag) override;
bool HandleProject(XMLTagHandler *&handle);
bool HandleLabelTrack(XMLTagHandler *&handle);
bool HandleNoteTrack(XMLTagHandler *&handle);
bool HandleTimeTrack(XMLTagHandler *&handle);
bool HandleWaveTrack(XMLTagHandler *&handle);
bool HandleTags(XMLTagHandler *&handle);
bool HandleTag(XMLTagHandler *&handle);
bool HandleLabel(XMLTagHandler *&handle);
bool HandleWaveClip(XMLTagHandler *&handle);
bool HandleSequence(XMLTagHandler *&handle);
bool HandleWaveBlock(XMLTagHandler *&handle);
bool HandleEnvelope(XMLTagHandler *&handle);
bool HandleControlPoint(XMLTagHandler *&handle);
bool HandleSimpleBlockFile(XMLTagHandler *&handle);
bool HandleSilentBlockFile(XMLTagHandler *&handle);
bool HandlePCMAliasBlockFile(XMLTagHandler *&handle);
void AddFile(sampleCount len,
const FilePath &filename = wxEmptyString,
sampleCount origin = 0,
int channel = 0);
bool AddSilence(sampleCount len);
bool AddSamples(const FilePath &filename,
sampleCount len,
sampleCount origin = 0,
int channel = 0);
bool SetError(const TranslatableString &msg);
bool SetWarning(const TranslatableString &msg);
private:
AudacityProject &mProject;
Tags *mTags;
// project tag values that will be set in the actual project if the
// import is successful
#define field(n, t) bool have##n; t n
struct
{
field(vpos, int);
field(h, double);
field(zoom, double);
field(sel0, double);
field(sel1, double);
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
field(selLow, double);
field(selHigh, double);
#endif
field(rate, double);
field(snapto, bool);
field(selectionformat, wxString);
field(audiotimeformat, wxString);
field(frequencyformat, wxString);
field(bandwidthformat, wxString);
} mProjectAttrs;
#undef field
typedef struct
{
WaveTrack *track;
WaveClip *clip;
FilePath path;
sampleCount len;
sampleCount origin;
int channel;
} fileinfo;
std::vector<fileinfo> mFiles;
sampleCount mTotalSamples;
sampleFormat mFormat;
unsigned long mSampleRate;
unsigned long mNumChannels;
stack mHandlers;
wxString mParentTag;
wxString mCurrentTag;
const wxChar **mAttrs;
wxFileName mProjDir;
using BlockFileMap = std::map<wxString, FilePath>;
BlockFileMap mFileMap;
ListOfTracks mTracks;
WaveTrack *mWaveTrack;
WaveClip *mClip;
ProgressResult mUpdateResult;
TranslatableString mErrorMsg;
};
AUPImportPlugin::AUPImportPlugin()
: ImportPlugin(FileExtensions(exts.begin(), exts.end()))
{
}
AUPImportPlugin::~AUPImportPlugin()
{
}
wxString AUPImportPlugin::GetPluginStringID()
{
return wxT("legacyaup");
}
TranslatableString AUPImportPlugin::GetPluginFormatDescription()
{
return DESC;
}
ImportHandle AUPImportPlugin::Open(const FilePath &fileName,
AudacityProject *project)
{
auto handle = std::make_unique<AUPImportFileHandle>(fileName, project);
if (!handle->Open())
{
// Error or not something that we recognize
return nullptr;
}
return handle;
}
static Importer::RegisteredImportPlugin registered
{
"AUP", std::make_unique<AUPImportPlugin>()
};
AUPImportFileHandle::AUPImportFileHandle(const FilePath &fileName,
AudacityProject *project)
: ImportFileHandle(fileName),
mProject(*project)
{
}
AUPImportFileHandle::~AUPImportFileHandle()
{
}
TranslatableString AUPImportFileHandle::GetFileDescription()
{
return DESC;
}
auto AUPImportFileHandle::GetFileUncompressedBytes() -> ByteCount
{
// TODO: Get Uncompressed byte count.
return 0;
}
ProgressResult AUPImportFileHandle::Import(TrackFactory *WXUNUSED(trackFactory),
TrackHolders &WXUNUSED(outTracks),
Tags *tags)
{
auto &history = ProjectHistory::Get(mProject);
auto &tracks = TrackList::Get(mProject);
auto &viewInfo = ViewInfo::Get(mProject);
auto &settings = ProjectSettings::Get(mProject);
auto &selman = ProjectSelectionManager::Get(mProject);
bool isDirty = history.GetDirty() || !tracks.empty();
mTotalSamples = 0;
mTags = tags;
CreateProgress();
mUpdateResult = ProgressResult::Success;
XMLFileReader xmlFile;
bool success = xmlFile.Parse(this, mFilename);
if (!success)
{
mTracks.clear();
AudacityMessageBox(
XO("Couldn't import the project:\n\n%s").Format(xmlFile.GetErrorStr()),
XO("Import Project"),
wxOK | wxCENTRE,
&GetProjectFrame(mProject));
return ProgressResult::Failed;
}
sampleCount processed = 0;
for (auto fi : mFiles)
{
mUpdateResult = mProgress->Update(processed.as_long_long(), mTotalSamples.as_long_long());
if (mUpdateResult != ProgressResult::Success)
{
return mUpdateResult;
}
mClip = fi.clip;
mWaveTrack = fi.track;
if (fi.path.empty())
{
AddSilence(fi.len);
}
else
{
AddSamples(fi.path, fi.len, fi.origin, fi.channel);
}
processed += fi.len;
}
if (mUpdateResult == ProgressResult::Failed || mUpdateResult == ProgressResult::Cancelled)
{
mTracks.clear();
return mUpdateResult;
}
// Copy the tracks we just created into the project.
for (auto &track : mTracks)
{
tracks.Add(track);
}
// Don't need our local track list anymore
mTracks.clear();
// If the active project is "dirty", then bypass the below updates as we don't
// want to going changing things the user may have already set up.
if (isDirty)
{
return mUpdateResult;
}
if (mProjectAttrs.haverate)
{
auto &bar = SelectionBar::Get(mProject);
bar.SetRate(mProjectAttrs.rate);
}
if (mProjectAttrs.havesnapto)
{
selman.AS_SetSnapTo(mProjectAttrs.snapto ? SNAP_NEAREST : SNAP_OFF);
}
if (mProjectAttrs.haveselectionformat)
{
selman.AS_SetSelectionFormat(NumericConverter::LookupFormat(NumericConverter::TIME, mProjectAttrs.selectionformat));
}
if (mProjectAttrs.haveaudiotimeformat)
{
selman.TT_SetAudioTimeFormat(NumericConverter::LookupFormat(NumericConverter::TIME, mProjectAttrs.audiotimeformat));
}
if (mProjectAttrs.havefrequencyformat)
{
selman.SSBL_SetFrequencySelectionFormatName(NumericConverter::LookupFormat(NumericConverter::TIME, mProjectAttrs.frequencyformat));
}
if (mProjectAttrs.havebandwidthformat)
{
selman.SSBL_SetBandwidthSelectionFormatName(NumericConverter::LookupFormat(NumericConverter::TIME, mProjectAttrs.bandwidthformat));
}
// PRL: It seems this must happen after SetSnapTo
if (mProjectAttrs.havevpos)
{
viewInfo.vpos = mProjectAttrs.vpos;
}
if (mProjectAttrs.haveh)
{
viewInfo.h = mProjectAttrs.h;
}
if (mProjectAttrs.havezoom)
{
viewInfo.SetZoom(mProjectAttrs.zoom);
}
if (mProjectAttrs.havesel0)
{
viewInfo.selectedRegion.setT0(mProjectAttrs.sel0);
}
if (mProjectAttrs.havesel1)
{
viewInfo.selectedRegion.setT1(mProjectAttrs.sel1);
}
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
if (mProjectAttrs.haveselLow)
{
viewInfo.selectedRegion.setF0(mProjectAttrs.selLow);
}
if (mProjectAttrs.haveselHigh)
{
viewInfo.selectedRegion.setF1(mProjectAttrs.selHigh);
}
#endif
return mUpdateResult;
}
wxInt32 AUPImportFileHandle::GetStreamCount()
{
return 1;
}
const TranslatableStrings &AUPImportFileHandle::GetStreamInfo()
{
static TranslatableStrings empty;
return empty;
}
void AUPImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use))
{
}
bool AUPImportFileHandle::Open()
{
wxFFile ff(mFilename, wxT("rb"));
if (ff.IsOpened())
{
char buf[256];
int numRead = ff.Read(buf, sizeof(buf));
ff.Close();
buf[sizeof(buf) - 1] = '\0';
if (!wxStrncmp(buf, wxT("AudacityProject"), 15))
{
AudacityMessageBox(
XO("This project was saved by Audacity version 1.0 or earlier. The format has\n"
"changed and this version of Audacity is unable to import the project.\n\n"
"Use a version of Audacity prior to v3.0.0 to upgrade the project and then\n"
"you may import it with this version of Audacity."),
XO("Import Project"),
wxOK | wxCENTRE,
&GetProjectFrame(mProject));
return false;
}
if (wxStrncmp(buf, "<?xml", 5) == 0 &&
(wxStrstr(buf, "<audacityproject") ||
wxStrstr(buf, "<project") ))
{
return true;
}
}
return false;
}
XMLTagHandler *AUPImportFileHandle::HandleXMLChild(const wxChar *tag)
{
return this;
}
void AUPImportFileHandle::HandleXMLEndTag(const wxChar *tag)
{
if (mUpdateResult != ProgressResult::Success)
{
return;
}
struct node node = mHandlers.back();
if (wxStrcmp(tag, wxT("wavetrack")) == 0)
{
WaveTrack *wt = static_cast<WaveTrack *>(node.handler);
wt->Flush();
}
else if (wxStrcmp(tag, wxT("waveclip")) == 0)
{
mClip = static_cast<WaveClip *>(node.handler);
mClip->Flush();
mClip->HandleXMLEndTag(tag);
}
else
{
if (node.handler)
{
node.handler->HandleXMLEndTag(tag);
}
}
mHandlers.pop_back();
if (mHandlers.size())
{
node = mHandlers.back();
mParentTag = node.parent;
mCurrentTag = node.tag;
}
}
bool AUPImportFileHandle::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
if (mUpdateResult != ProgressResult::Success)
{
return false;
}
mParentTag = mCurrentTag;
mCurrentTag = tag;
mAttrs = attrs;
XMLTagHandler *handler = nullptr;
bool success = false;
if (mCurrentTag.IsSameAs(wxT("project")) ||
mCurrentTag.IsSameAs(wxT("audacityproject")))
{
success = HandleProject(handler);
}
else if (mCurrentTag.IsSameAs(wxT("labeltrack")))
{
success = HandleLabelTrack(handler);
}
else if (mCurrentTag.IsSameAs(wxT("notetrack")))
{
success = HandleNoteTrack(handler);
}
else if (mCurrentTag.IsSameAs(wxT("timetrack")))
{
success = HandleTimeTrack(handler);
}
else if (mCurrentTag.IsSameAs(wxT("wavetrack")))
{
success = HandleWaveTrack(handler);
}
else if (mCurrentTag.IsSameAs(wxT("tags")))
{
success = HandleTags(handler);
}
else if (mCurrentTag.IsSameAs(wxT("tag")))
{
success = HandleTag(handler);
}
else if (mCurrentTag.IsSameAs(wxT("label")))
{
success = HandleLabel(handler);
}
else if (mCurrentTag.IsSameAs(wxT("waveclip")))
{
success = HandleWaveClip(handler);
}
else if (mCurrentTag.IsSameAs(wxT("sequence")))
{
success = HandleSequence(handler);
}
else if (mCurrentTag.IsSameAs(wxT("waveblock")))
{
success = HandleWaveBlock(handler);
}
else if (mCurrentTag.IsSameAs(wxT("envelope")))
{
success = HandleEnvelope(handler);
}
else if (mCurrentTag.IsSameAs(wxT("controlpoint")))
{
success = HandleControlPoint(handler);
}
else if (mCurrentTag.IsSameAs(wxT("simpleblockfile")))
{
success = HandleSimpleBlockFile(handler);
}
else if (mCurrentTag.IsSameAs(wxT("silentblockfile")))
{
success = HandleSilentBlockFile(handler);
}
else if (mCurrentTag.IsSameAs(wxT("pcmaliasblockfile")))
{
success = HandlePCMAliasBlockFile(handler);
}
if (!success || (handler && !handler->HandleXMLTag(tag, attrs)))
{
return SetError(XO("Internal error in importer...tag not recognized"));
}
mHandlers.push_back({mParentTag, mCurrentTag, handler});
return true;
}
bool AUPImportFileHandle::HandleProject(XMLTagHandler *&handler)
{
auto &fileMan = ProjectFileManager::Get(mProject);
auto &window = GetProjectFrame(mProject);
int requiredTags = 0;
while (*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
long lValue;
long long llValue;
double dValue;
if (!value)
{
break;
}
if (!XMLValueChecker::IsGoodString(value))
{
return SetError(XO("Invalid project '%s' attribute.").Format(attr));
}
wxString strValue = value;
#define set(f, v) (mProjectAttrs.have ## f = true, mProjectAttrs.f = v)
// ViewInfo
if (!wxStrcmp(attr, wxT("vpos")))
{
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&lValue) || (lValue < 0))
{
return SetError(XO("Invalid project 'vpos' attribute."));
}
set(vpos, (int) lValue);
}
else if (!wxStrcmp(attr, wxT("h")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'h' attribute."));
}
set(h, dValue);
}
else if (!wxStrcmp(attr, wxT("zoom")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'zoom' attribute."));
}
set(zoom, dValue);
}
// Viewinfo.SelectedRegion
else if (!wxStrcmp(attr, wxT("sel0")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'sel0' attribute."));
}
set(sel0, dValue);
}
else if (!wxStrcmp(attr, wxT("sel1")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'sel1' attribute."));
}
set(sel1, dValue);
}
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
else if (!wxStrcmp(attr, wxT("selLow")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'selLow' attribute."));
}
set(selLow, dValue);
}
else if (!wxStrcmp(attr, wxT("selHigh")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'selHigh' attribute."));
}
set(selHigh, dValue);
}
#endif
else if (!wxStrcmp(attr, wxT("version")))
{
requiredTags++;
}
else if (!wxStrcmp(attr, wxT("audacityversion")))
{
requiredTags++;
}
else if (!wxStrcmp(attr, wxT("projname")))
{
requiredTags++;
mProjDir = mFilename;
wxString altname = mProjDir.GetName() + wxT("-data");
mProjDir.SetFullName(wxEmptyString);
wxString projName = value;
bool found = false;
// First try to load the data files based on the _data dir given in the .aup file
if (!projName.empty())
{
mProjDir.AppendDir(projName);
if (!mProjDir.DirExists())
{
projName.clear();
}
}
// If that fails then try to use the filename of the .aup as the base directory
// This is because unzipped projects e.g. those that get transfered between mac-pc
// may have encoding issues and end up expanding the wrong filenames for certain
// international characters (such as capital 'A' with an umlaut.)
if (projName.empty())
{
projName = altname;
mProjDir.AppendDir(projName);
if (!mProjDir.DirExists())
{
projName.clear();
}
}
// No luck...complain and bail
if (projName.empty())
{
AudacityMessageBox(
XO("Couldn't find the project data folder: \"%s\"").Format(*value),
XO("Error Opening Project"),
wxOK | wxCENTRE,
&window);
return false;
}
// Collect and hash the file names within the project directory
wxArrayString files;
size_t cnt = wxDir::GetAllFiles(mProjDir.GetFullPath(),
&files,
"*.*");
for (size_t i = 0; i < cnt; ++i)
{
FilePath fn = files[i];
mFileMap[wxFileNameFromPath(fn)] = fn;
}
}
else if (!wxStrcmp(attr, wxT("rate")))
{
if (!Internat::CompatibleToDouble(value, &dValue) || (dValue < 0.0))
{
return SetError(XO("Invalid project 'selLow' attribute."));
}
set(rate, dValue);
}
else if (!wxStrcmp(attr, wxT("snapto")))
{
set(snapto, (strValue == wxT("on") ? true : false));
}
else if (!wxStrcmp(attr, wxT("selectionformat")))
{
set(selectionformat, strValue);
}
else if (!wxStrcmp(attr, wxT("audiotimeformat")))
{
set(audiotimeformat, strValue);
}
else if (!wxStrcmp(attr, wxT("frequencyformat")))
{
set(frequencyformat, strValue);
}
else if (!wxStrcmp(attr, wxT("bandwidthformat")))
{
set(bandwidthformat, strValue);
}
#undef set
}
if (requiredTags < 3)
{
return false;
}
// Do not set the handler - already handled
return true;
}
bool AUPImportFileHandle::HandleLabelTrack(XMLTagHandler *&handler)
{
auto &trackFactory = TrackFactory::Get(mProject);
mTracks.push_back(trackFactory.NewLabelTrack());
handler = mTracks.back().get();
return true;
}
bool AUPImportFileHandle::HandleNoteTrack(XMLTagHandler *&handler)
{
#if defined(USE_MIDI)
auto &trackFactory = TrackFactory::Get(mProject);
mTracks.push_back(trackFactory.NewNoteTrack());
handler = mTracks.back().get();
return true;
#else
AudacityMessageBox(
XO("MIDI tracks found in project file, but this build of Audacity does not include MIDI support, bypassing track."),
XO("Project Import"),
wxOK | wxICON_EXCLAMATION | wxCENTRE,
&GetProjectFrame(mProject));
return false;
#endif
}
bool AUPImportFileHandle::HandleTimeTrack(XMLTagHandler *&handler)
{
auto &tracks = TrackList::Get(mProject);
if (*tracks.Any<TimeTrack>().begin())
{
AudacityMessageBox(
XO("The active project already has a time track and one was encountered in the project being imported, bypassing time track in project file."),
XO("Project Import"),
wxOK | wxICON_EXCLAMATION | wxCENTRE,
&GetProjectFrame(mProject));
return false;
}
auto &trackFactory = TrackFactory::Get(mProject);
mTracks.push_back(trackFactory.NewTimeTrack());
handler = mTracks.back().get();
return true;
}
bool AUPImportFileHandle::HandleWaveTrack(XMLTagHandler *&handler)
{
auto &trackFactory = TrackFactory::Get(mProject);
mTracks.push_back(trackFactory.NewWaveTrack());
handler = mTracks.back().get();
mWaveTrack = static_cast<WaveTrack *>(handler);
// No active clip. In early versions of Audacity, there was a single
// implied clip so we'll create a clip when the first "sequence" is
// found.
mClip = nullptr;
return true;
}
bool AUPImportFileHandle::HandleTags(XMLTagHandler *&handler)
{
wxString n;
wxString v;
// Support for legacy tags
while(*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
if (!value)
{
break;
}
// Ignore empty tags
if (!*value)
{
continue;
}
if (!XMLValueChecker::IsGoodString(attr) || !XMLValueChecker::IsGoodString(value))
{
// Log it???
return false;
}
if (!wxStrcmp(attr, "id3v2"))
{
continue;
}
else if (!wxStrcmp(attr, "track"))
{
n = wxT("TRACKNUMBER");
}
else
{
n = attr;
n.MakeUpper();
}
mTags->SetTag(n, value);
}
// Do not set the handler - already handled
return true;
}
bool AUPImportFileHandle::HandleTag(XMLTagHandler *&handler)
{
if (!mParentTag.IsSameAs(wxT("tags")))
{
return false;
}
wxString n, v;
while (*mAttrs)
{
wxString attr = *mAttrs++;
if (attr.empty())
{
break;
}
wxString value = *mAttrs++;
if (!XMLValueChecker::IsGoodString(attr) || !XMLValueChecker::IsGoodString(value))
{
break;
}
if (attr == wxT("name"))
{
n = value;
}
else if (attr == wxT("value"))
{
v = value;
}
}
if (n == wxT("id3v2"))
{
// LLL: This is obsolete, but it must be handled and ignored.
}
else
{
mTags->SetTag(n, v);
}
// Do not set the handler - already handled
return true;
}
bool AUPImportFileHandle::HandleLabel(XMLTagHandler *&handler)
{
if (!mParentTag.IsSameAs(wxT("labeltrack")))
{
return false;
}
// The parent handler also handles this tag
handler = mHandlers.back().handler;
return true;
}
bool AUPImportFileHandle::HandleWaveClip(XMLTagHandler *&handler)
{
struct node node = mHandlers.back();
if (mParentTag.IsSameAs(wxT("wavetrack")))
{
WaveTrack *wavetrack = static_cast<WaveTrack *>(node.handler);
handler = wavetrack->CreateClip();
}
else if (mParentTag.IsSameAs(wxT("waveclip")))
{
// Nested wave clips are cut lines
WaveClip *waveclip = static_cast<WaveClip *>(node.handler);
handler = waveclip->HandleXMLChild(mCurrentTag);
}
mClip = static_cast<WaveClip *>(handler);
return handler;
}
bool AUPImportFileHandle::HandleEnvelope(XMLTagHandler *&handler)
{
struct node node = mHandlers.back();
if (mParentTag.IsSameAs(wxT("timetrack")))
{
TimeTrack *timetrack = static_cast<TimeTrack *>(node.handler);
handler = timetrack->GetEnvelope();
}
// Earlier versions of Audacity had a single implied waveclip, so for
// these versions, we get or create the only clip in the track.
else if (mParentTag.IsSameAs(wxT("wavetrack")))
{
handler = mWaveTrack->RightmostOrNewClip()->GetEnvelope();
}
// Nested wave clips are cut lines
else if (mParentTag.IsSameAs(wxT("waveclip")))
{
WaveClip *waveclip = static_cast<WaveClip *>(node.handler);
handler = waveclip->GetEnvelope();
}
return handler;
}
bool AUPImportFileHandle::HandleControlPoint(XMLTagHandler *&handler)
{
struct node node = mHandlers.back();
if (mParentTag.IsSameAs(wxT("envelope")))
{
Envelope *envelope = static_cast<Envelope *>(node.handler);
handler = envelope->HandleXMLChild(mCurrentTag);
}
return handler;
}
bool AUPImportFileHandle::HandleSequence(XMLTagHandler *&handler)
{
struct node node = mHandlers.back();
WaveClip *waveclip = static_cast<WaveClip *>(node.handler);
while(*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
if (!value)
{
break;
}
long long nValue = 0;
const wxString strValue = value; // promote string, we need this for all
if (!wxStrcmp(attr, wxT("maxsamples")))
{
// This attribute is a sample count, so can be 64bit
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
{
return SetError(XO("Invalid sequence 'maxsamples' attribute."));
}
// Dominic, 12/10/2006:
// Let's check that maxsamples is >= 1024 and <= 64 * 1024 * 1024
// - that's a pretty wide range of reasonable values.
if ((nValue < 1024) || (nValue > 64 * 1024 * 1024))
{
return SetError(XO("Invalid sequence 'maxsamples' attribute."));
}
}
else if (!wxStrcmp(attr, wxT("sampleformat")))
{
// This attribute is a sample format, normal int
long fValue;
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&fValue) || (fValue < 0) || !XMLValueChecker::IsValidSampleFormat(fValue))
{
return SetError(XO("Invalid sequence 'sampleformat' attribute."));
}
mFormat = (sampleFormat) fValue;
}
else if (!wxStrcmp(attr, wxT("numsamples")))
{
// This attribute is a sample count, so can be 64bit
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
{
return SetError(XO("Invalid sequence 'numsamples' attribute."));
}
}
}
// Do not set the handler - already handled
return true;
}
bool AUPImportFileHandle::HandleWaveBlock(XMLTagHandler *&handler)
{
while(*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
long long nValue = 0;
if (!value)
{
break;
}
const wxString strValue = value;
if (!wxStrcmp(attr, wxT("start")))
{
// making sure that values > 2^31 are OK because long clips will need them.
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
{
return SetError(XO("Unable to parse the waveblock 'start' attribute"));
}
}
}
// Do not set the handler - already handled
return true;
}
bool AUPImportFileHandle::HandleSimpleBlockFile(XMLTagHandler *&handler)
{
FilePath filename;
sampleCount len = 0;
while (*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
long long nValue;
if (!value)
{
break;
}
const wxString strValue = value;
// Can't use XMLValueChecker::IsGoodFileName here, but do part of its test.
if (!wxStricmp(attr, wxT("filename")))
{
if (XMLValueChecker::IsGoodFileString(strValue))
{
if (mFileMap.find(strValue) != mFileMap.end())
{
filename = mFileMap[strValue];
}
else
{
SetWarning(XO("Missing project file %s\n\nInserting silence instead.")
.Format(strValue));
}
}
}
else if (!wxStrcmp(attr, wxT("len")))
{
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue <= 0))
{
return SetError(XO("Missing or invalid simpleblockfile 'len' attribute."));
}
len = nValue;
}
}
// Do not set the handler - already handled
AddFile(len, filename);
return true;
if (filename.empty())
{
// return AddSilence(len);
}
// return AddSamples(filename, len);
return true;
}
bool AUPImportFileHandle::HandleSilentBlockFile(XMLTagHandler *&handler)
{
FilePath filename;
sampleCount len = 0;
while (*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
long long nValue;
if (!value)
{
break;
}
const wxString strValue = value;
if (!wxStrcmp(attr, wxT("len")))
{
if (!XMLValueChecker::IsGoodInt64(value) || !strValue.ToLongLong(&nValue) | !(nValue > 0))
{
return SetError(XO("Missing or invalid silentblockfile 'len' attribute."));
}
len = nValue;
}
}
// Do not set the handler - already handled
AddFile(len);
return true;
return AddSilence(len);
}
bool AUPImportFileHandle::HandlePCMAliasBlockFile(XMLTagHandler *&handler)
{
wxFileName filename;
sampleCount start = 0;
sampleCount len = 0;
int channel = 0;
wxString name;
while (*mAttrs)
{
const wxChar *attr = *mAttrs++;
const wxChar *value = *mAttrs++;
long long nValue;
if (!value)
{
break;
}
const wxString strValue = value;
if (!wxStricmp(attr, wxT("aliasfile")))
{
if (XMLValueChecker::IsGoodPathName(strValue))
{
filename.Assign(strValue);
}
else if (XMLValueChecker::IsGoodFileName(strValue, mProjDir.GetPath()))
{
// Allow fallback of looking for the file name, located in the data directory.
filename.Assign(mProjDir.GetPath(), strValue);
}
else if (XMLValueChecker::IsGoodPathString(strValue))
{
// If the aliased file is missing, we failed XMLValueChecker::IsGoodPathName()
// and XMLValueChecker::IsGoodFileName, because both do existence tests.
SetWarning(XO("Missing alias file %s\n\nInserting silence instead.")
.Format(strValue));
}
}
else if (!wxStricmp(attr, wxT("aliasstart")))
{
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
{
return SetError(XO("Missing or invalid pcmaliasblockfile 'aliasstart' attribute."));
}
start = nValue;
}
else if (!wxStricmp(attr, wxT("aliaslen")))
{
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue <= 0))
{
return SetError(XO("Missing or invalid pcmaliasblockfile 'aliaslen' attribute."));
}
len = nValue;
}
else if (!wxStricmp(attr, wxT("aliaschannel")))
{
long nValue;
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) || (nValue < 0))
{
return SetError(XO("Missing or invalid pcmaliasblockfile 'aliaslen' attribute."));
}
channel = nValue;
}
}
// Do not set the handler - already handled
AddFile(len, filename.GetFullPath(), start, channel);
return true;
if (!filename.IsOk())
{
return AddSilence(len);
}
return AddSamples(filename.GetFullPath(), len, start, channel);
}
void AUPImportFileHandle::AddFile(sampleCount len,
const FilePath &filename /* = wxEmptyString */,
sampleCount origin /* = 0 */,
int channel /* = 0 */)
{
fileinfo fi = {};
fi.track = mWaveTrack;
fi.clip = mClip;
fi.path = filename;
fi.len = len;
fi.origin = origin,
fi.channel = channel;
mFiles.push_back(fi);
mTotalSamples += len;
}
bool AUPImportFileHandle::AddSilence(sampleCount len)
{
wxASSERT(mClip || mWaveTrack);
if (mClip)
{
mClip->InsertSilence(mClip->GetEndTime(), mWaveTrack->LongSamplesToTime(len));
}
else if (mWaveTrack)
{
mWaveTrack->InsertSilence(mWaveTrack->GetEndTime(), mWaveTrack->LongSamplesToTime(len));
}
return true;
}
// All errors that occur here will simply insert silence and allow the
// import to continue.
bool AUPImportFileHandle::AddSamples(const FilePath &filename,
sampleCount len,
sampleCount origin /* = 0 */,
int channel /* = 0 */)
{
// Third party library has its own type alias, check it before
// adding origin + size_t
static_assert(sizeof(sampleCount::type) <= sizeof(sf_count_t),
"Type sf_count_t is too narrow to hold a sampleCount");
SF_INFO info;
memset(&info, 0, sizeof(info));
wxFile f; // will be closed when it goes out of scope
SNDFILE *sf = nullptr;
bool success = false;
auto cleanup = finally([&]
{
if (!success)
{
SetWarning(XO("Error while processing %s\n\nInserting silence.").Format(filename));
AddSilence(len);
}
if (sf)
{
sf_close(sf);
}
});
if (!f.Open(filename))
{
SetWarning(XO("Failed to open %s").Format(filename));
return true;
}
// Even though there is an sf_open() that takes a filename, use the one that
// takes a file descriptor since wxWidgets can open a file with a Unicode name and
// libsndfile can't (under Windows).
sf = sf_open_fd(f.fd(), SFM_READ, &info, FALSE);
if (!sf)
{
SetWarning(XO("Failed to open %s").Format(filename));
return true;
}
if (origin > 0)
{
if (sf_seek(sf, origin.as_long_long(), SEEK_SET) < 0)
{
SetWarning(XO("Failed to seek to position %lld in %s")
.Format(origin.as_long_long(), filename));
return true;
}
}
sampleFormat format = mFormat;
sf_count_t cnt = len.as_size_t();
int channels = info.channels;
wxASSERT(channels >= 1);
wxASSERT(channel < channels);
SampleBuffer buffer(cnt, format);
samplePtr bufptr = buffer.ptr();
size_t framesRead = 0;
if (channels == 1 && format == int16Sample && sf_subtype_is_integer(info.format))
{
// If both the src and dest formats are integer formats,
// read integers directly from the file, conversions not needed
framesRead = sf_readf_short(sf, (short *) bufptr, cnt);
}
else if (channels == 1 && format == int24Sample && sf_subtype_is_integer(info.format))
{
framesRead = sf_readf_int(sf, (int *) bufptr, cnt);
if (framesRead != cnt)
{
SetWarning(XO("Unable to read %lld samples from %s")
.Format(cnt, filename));
return true;
}
// libsndfile gave us the 3 byte sample in the 3 most
// significant bytes -- we want it in the 3 least
// significant bytes.
int *intPtr = (int *) bufptr;
for (size_t i = 0; i < framesRead; i++)
{
intPtr[i] = intPtr[i] >> 8;
}
}
else if (format == int16Sample && !sf_subtype_more_than_16_bits(info.format))
{
// Special case: if the file is in 16-bit (or less) format,
// and the calling method wants 16-bit data, go ahead and
// read 16-bit data directly. This is a pretty common
// case, as most audio files are 16-bit.
SampleBuffer temp(cnt * channels, int16Sample);
short *tmpptr = (short *) temp.ptr();
framesRead = sf_readf_short(sf, tmpptr, cnt);
if (framesRead != cnt)
{
SetWarning(XO("Unable to read %lld samples from %s")
.Format(cnt, filename));
return true;
}
for (size_t i = 0; i < framesRead; i++)
{
((short *)bufptr)[i] = tmpptr[(channels * i) + channel];
}
}
else
{
// Otherwise, let libsndfile handle the conversion and
// scaling, and pass us normalized data as floats. We can
// then convert to whatever format we want.
SampleBuffer tmpbuf(cnt * channels, floatSample);
float *tmpptr = (float *) tmpbuf.ptr();
framesRead = sf_readf_float(sf, tmpptr, cnt);
if (framesRead != cnt)
{
SetWarning(XO("Unable to read %lld samples from %s")
.Format(cnt, filename));
return true;
}
CopySamples((samplePtr)(tmpptr + channel),
floatSample,
bufptr,
format,
framesRead,
true /* high quality by default */,
channels /* source stride */);
}
wxASSERT(mClip || mWaveTrack);
// Add the samples to the clip/track
if (mClip)
{
mClip->Append(bufptr, format, cnt);
}
else if (mWaveTrack)
{
mWaveTrack->Append(bufptr, format, cnt);
}
// Let the finally block know everything is good
success = true;
return true;
}
bool AUPImportFileHandle::SetError(const TranslatableString &msg)
{
wxLogError(msg.Translation());
if (mErrorMsg.empty())
{
mErrorMsg = msg;
}
mUpdateResult = ProgressResult::Failed;
return false;
}
bool AUPImportFileHandle::SetWarning(const TranslatableString &msg)
{
wxLogWarning(msg.Translation());
if (mErrorMsg.empty())
{
mErrorMsg = msg;
}
return false;
}